mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Fleet] Add ssl fields to agent binary source settings (#213211)
closes https://github.com/elastic/kibana/issues/207324 follow up of https://github.com/elastic/kibana/issues/207322 ## Summary Add ssl fields to agent binary source settings. The new fields allow users to set a TLS connection to the agent binary source uri. - The cert key will be stored either as an encrypted SO or a secret (latter option will be available once fleet server will have this functionality: https://github.com/elastic/fleet-server/issues/4470). - The secret field is only available when the feature flag `enableSSLSecrets` is enabled, otherwise the cert key is saved as an encrypted SO. <details> <summary>Screenshots</summary> <img width="809" alt="Screenshot 2025-03-11 at 14 53 44" src="https://github.com/user-attachments/assets/e93a04cf-c699-4e13-8cb6-870986197f92" /> <img width="804" alt="Screenshot 2025-03-11 at 14 53 34" src="https://github.com/user-attachments/assets/c2c13c8f-e65c-4843-a538-d317e1359bf0" /> Generated policy: <img width="797" alt="Screenshot 2025-03-06 at 17 43 02" src="https://github.com/user-attachments/assets/12411fea-9a8b-4ee9-aa7c-123c6aefea4a" /> </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
ed7178674c
commit
382630ecd1
32 changed files with 3462 additions and 689 deletions
|
@ -8703,6 +8703,55 @@
|
|||
"description": "The ID of the proxy to use for this download source. See the proxies API for more information.",
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -8807,6 +8856,55 @@
|
|||
"description": "The ID of the proxy to use for this download source. See the proxies API for more information.",
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -8846,6 +8944,55 @@
|
|||
"description": "The ID of the proxy to use for this download source. See the proxies API for more information.",
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -9015,6 +9162,55 @@
|
|||
"description": "The ID of the proxy to use for this download source. See the proxies API for more information.",
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -9113,6 +9309,55 @@
|
|||
"description": "The ID of the proxy to use for this download source. See the proxies API for more information.",
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -9152,6 +9397,55 @@
|
|||
"description": "The ID of the proxy to use for this download source. See the proxies API for more information.",
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -16374,8 +16668,56 @@
|
|||
"download": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"key"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"sourceURI": {
|
||||
"type": "string"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"renegotiation": {
|
||||
"type": "string"
|
||||
},
|
||||
"verification_mode": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
|
@ -8703,6 +8703,55 @@
|
|||
"description": "The ID of the proxy to use for this download source. See the proxies API for more information.",
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -8807,6 +8856,55 @@
|
|||
"description": "The ID of the proxy to use for this download source. See the proxies API for more information.",
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -8846,6 +8944,55 @@
|
|||
"description": "The ID of the proxy to use for this download source. See the proxies API for more information.",
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -9015,6 +9162,55 @@
|
|||
"description": "The ID of the proxy to use for this download source. See the proxies API for more information.",
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -9113,6 +9309,55 @@
|
|||
"description": "The ID of the proxy to use for this download source. See the proxies API for more information.",
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -9152,6 +9397,55 @@
|
|||
"description": "The ID of the proxy to use for this download source. See the proxies API for more information.",
|
||||
"nullable": true,
|
||||
"type": "string"
|
||||
},
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -16374,8 +16668,56 @@
|
|||
"download": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"secrets": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"key": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"key"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"sourceURI": {
|
||||
"type": "string"
|
||||
},
|
||||
"ssl": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"certificate_authorities": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"renegotiation": {
|
||||
"type": "string"
|
||||
},
|
||||
"verification_mode": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
|
@ -12995,6 +12995,36 @@ paths:
|
|||
description: The ID of the proxy to use for this download source. See the proxies API for more information.
|
||||
nullable: true
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
key:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
|
@ -13065,6 +13095,36 @@ paths:
|
|||
description: The ID of the proxy to use for this download source. See the proxies API for more information.
|
||||
nullable: true
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
key:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- host
|
||||
|
@ -13094,6 +13154,36 @@ paths:
|
|||
description: The ID of the proxy to use for this download source. See the proxies API for more information.
|
||||
nullable: true
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
key:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
|
@ -13208,6 +13298,36 @@ paths:
|
|||
description: The ID of the proxy to use for this download source. See the proxies API for more information.
|
||||
nullable: true
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
key:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
|
@ -13273,6 +13393,36 @@ paths:
|
|||
description: The ID of the proxy to use for this download source. See the proxies API for more information.
|
||||
nullable: true
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
key:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- host
|
||||
|
@ -13302,6 +13452,36 @@ paths:
|
|||
description: The ID of the proxy to use for this download source. See the proxies API for more information.
|
||||
nullable: true
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
key:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
|
@ -17976,8 +18156,40 @@ paths:
|
|||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- key
|
||||
sourceURI:
|
||||
type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
key:
|
||||
type: string
|
||||
renegotiation:
|
||||
type: string
|
||||
verification_mode:
|
||||
type: string
|
||||
required:
|
||||
- sourceURI
|
||||
features:
|
||||
|
|
|
@ -15226,6 +15226,36 @@ paths:
|
|||
description: The ID of the proxy to use for this download source. See the proxies API for more information.
|
||||
nullable: true
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
key:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
|
@ -15295,6 +15325,36 @@ paths:
|
|||
description: The ID of the proxy to use for this download source. See the proxies API for more information.
|
||||
nullable: true
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
key:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- host
|
||||
|
@ -15324,6 +15384,36 @@ paths:
|
|||
description: The ID of the proxy to use for this download source. See the proxies API for more information.
|
||||
nullable: true
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
key:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
|
@ -15436,6 +15526,36 @@ paths:
|
|||
description: The ID of the proxy to use for this download source. See the proxies API for more information.
|
||||
nullable: true
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
key:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
|
@ -15500,6 +15620,36 @@ paths:
|
|||
description: The ID of the proxy to use for this download source. See the proxies API for more information.
|
||||
nullable: true
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
key:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- host
|
||||
|
@ -15529,6 +15679,36 @@ paths:
|
|||
description: The ID of the proxy to use for this download source. See the proxies API for more information.
|
||||
nullable: true
|
||||
type: string
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
key:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
|
@ -20195,8 +20375,40 @@ paths:
|
|||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
secrets:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- key
|
||||
sourceURI:
|
||||
type: string
|
||||
ssl:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
certificate:
|
||||
type: string
|
||||
certificate_authorities:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
key:
|
||||
type: string
|
||||
renegotiation:
|
||||
type: string
|
||||
verification_mode:
|
||||
type: string
|
||||
required:
|
||||
- sourceURI
|
||||
features:
|
||||
|
|
|
@ -2105,6 +2105,7 @@
|
|||
}
|
||||
},
|
||||
"ingest-download-sources": {
|
||||
"dynamic": false,
|
||||
"properties": {
|
||||
"host": {
|
||||
"type": "keyword"
|
||||
|
|
|
@ -125,7 +125,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
|
|||
"infrastructure-monitoring-log-view": "5f86709d3c27aed7a8379153b08ee5d3d90d77f5",
|
||||
"infrastructure-ui-source": "113182d6895764378dfe7fa9fa027244f3a457c4",
|
||||
"ingest-agent-policies": "cfe66f4aeca8f53b26bd4ddb0e956de1637d774e",
|
||||
"ingest-download-sources": "279a68147e62e4d8858c09ad1cf03bd5551ce58d",
|
||||
"ingest-download-sources": "5be99940d6b5f9121b2fd279708d14e2bc0bde26",
|
||||
"ingest-outputs": "6743521f501bd77b1523dbb1df48d7c47fdad529",
|
||||
"ingest-package-policies": "6a80000fdf2544f2485b0c6a51ecc434b6a12987",
|
||||
"ingest_manager_settings": "111a616eb72627c002029c19feb9e6c439a10505",
|
||||
|
|
|
@ -8,9 +8,7 @@
|
|||
import type { SecurityRoleDescriptor } from '@elastic/elasticsearch/lib/api/types';
|
||||
|
||||
import type { agentPolicyStatuses } from '../../constants';
|
||||
import type { MonitoringType, PolicySecretReference, ValueOf } from '..';
|
||||
|
||||
import type { SOSecret } from '..';
|
||||
import type { BaseSSLSecrets, MonitoringType, PolicySecretReference, ValueOf } from '..';
|
||||
|
||||
import type { PackagePolicy, PackagePolicyPackage } from './package_policy';
|
||||
import type { Output } from './output';
|
||||
|
@ -179,6 +177,11 @@ export interface FullAgentPolicyMonitoring {
|
|||
};
|
||||
};
|
||||
}
|
||||
export interface FullAgentPolicyDownload {
|
||||
sourceURI: string;
|
||||
ssl?: BaseSSLConfig;
|
||||
secrets?: BaseSSLSecrets;
|
||||
}
|
||||
|
||||
export interface FullAgentPolicy {
|
||||
id: string;
|
||||
|
@ -198,7 +201,7 @@ export interface FullAgentPolicy {
|
|||
revision?: number;
|
||||
agent?: {
|
||||
monitoring: FullAgentPolicyMonitoring;
|
||||
download: { sourceURI: string };
|
||||
download: FullAgentPolicyDownload;
|
||||
features: Record<string, { enabled: boolean }>;
|
||||
protection?: {
|
||||
enabled: boolean;
|
||||
|
@ -239,9 +242,7 @@ export interface FullAgentPolicyFleetConfig {
|
|||
proxy_url?: string;
|
||||
proxy_headers?: any;
|
||||
ssl?: BaseSSLConfig;
|
||||
secrets?: {
|
||||
ssl?: { key?: SOSecret };
|
||||
};
|
||||
secrets?: BaseSSLSecrets;
|
||||
}
|
||||
|
||||
export interface FullAgentPolicyKibanaConfig {
|
||||
|
|
|
@ -5,11 +5,19 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { BaseSSLSecrets } from './secret';
|
||||
|
||||
export interface DownloadSourceBase {
|
||||
name: string;
|
||||
host: string;
|
||||
is_default: boolean;
|
||||
proxy_id?: string | null;
|
||||
ssl?: {
|
||||
certificate_authorities?: string[];
|
||||
certificate?: string;
|
||||
key?: string;
|
||||
};
|
||||
secrets?: BaseSSLSecrets;
|
||||
}
|
||||
|
||||
export type DownloadSource = DownloadSourceBase & {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { outputType } from '../../constants';
|
||||
import type { ValueOf } from '..';
|
||||
import type { BaseSSLSecrets, ValueOf } from '..';
|
||||
import type { kafkaAuthType, kafkaCompressionType, kafkaSaslMechanism } from '../../constants';
|
||||
import type { kafkaPartitionType } from '../../constants';
|
||||
import type { kafkaTopicWhenType } from '../../constants';
|
||||
|
@ -47,11 +47,7 @@ interface NewBaseOutput {
|
|||
proxy_id?: string | null;
|
||||
shipper?: ShipperOutput | null;
|
||||
allow_edit?: string[];
|
||||
secrets?: {
|
||||
ssl?: {
|
||||
key?: SOSecret;
|
||||
};
|
||||
};
|
||||
secrets?: BaseSSLSecrets;
|
||||
preset?: OutputPreset;
|
||||
}
|
||||
|
||||
|
@ -62,13 +58,7 @@ export interface NewElasticsearchOutput extends NewBaseOutput {
|
|||
export interface NewRemoteElasticsearchOutput extends NewBaseOutput {
|
||||
type: OutputType['RemoteElasticsearch'];
|
||||
service_token?: string | null;
|
||||
secrets?: {
|
||||
service_token?: SOSecret;
|
||||
kibana_api_key?: SOSecret;
|
||||
ssl?: {
|
||||
key?: SOSecret;
|
||||
};
|
||||
};
|
||||
secrets?: RemoteESOutputSecrets;
|
||||
sync_integrations?: boolean;
|
||||
kibana_url?: string | null;
|
||||
kibana_api_key?: string | null;
|
||||
|
@ -76,11 +66,6 @@ export interface NewRemoteElasticsearchOutput extends NewBaseOutput {
|
|||
|
||||
export interface NewLogstashOutput extends NewBaseOutput {
|
||||
type: OutputType['Logstash'];
|
||||
secrets?: {
|
||||
ssl?: {
|
||||
key?: SOSecret;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export type NewOutput =
|
||||
|
@ -140,10 +125,13 @@ export interface KafkaOutput extends NewBaseOutput {
|
|||
timeout?: number;
|
||||
broker_timeout?: number;
|
||||
required_acks?: ValueOf<KafkaAcknowledgeReliabilityLevel>;
|
||||
secrets?: {
|
||||
password?: SOSecret;
|
||||
ssl?: {
|
||||
key?: SOSecret;
|
||||
};
|
||||
};
|
||||
secrets?: KafkaOutputSecrets;
|
||||
}
|
||||
|
||||
interface KafkaOutputSecrets extends BaseSSLSecrets {
|
||||
password?: SOSecret;
|
||||
}
|
||||
interface RemoteESOutputSecrets extends BaseSSLSecrets {
|
||||
service_token?: SOSecret;
|
||||
kibana_api_key?: SOSecret;
|
||||
}
|
||||
|
|
|
@ -45,3 +45,7 @@ export interface DeletedSecretReference {
|
|||
id: string;
|
||||
deleted: boolean;
|
||||
}
|
||||
|
||||
export interface BaseSSLSecrets {
|
||||
ssl?: { key?: SOSecret };
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { DownloadSourceBase, DownloadSource } from '../models';
|
||||
import type { DownloadSourceBase, DownloadSource, BaseSSLSecrets } from '../models';
|
||||
|
||||
import type { ListResult } from './common';
|
||||
|
||||
|
@ -31,6 +31,12 @@ export interface PutDownloadSourceRequest {
|
|||
name: string;
|
||||
host: string;
|
||||
is_default?: boolean;
|
||||
ssl?: {
|
||||
certificate_authorities?: string[];
|
||||
certificate?: string;
|
||||
key?: string;
|
||||
};
|
||||
secrets?: BaseSSLSecrets;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -41,6 +47,12 @@ export interface PostDownloadSourceRequest {
|
|||
host: string;
|
||||
is_default?: boolean;
|
||||
proxy_id?: string | null;
|
||||
ssl?: {
|
||||
certificate_authorities?: string[];
|
||||
certificate?: string;
|
||||
key?: string;
|
||||
};
|
||||
secrets?: BaseSSLSecrets;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useMemo, useEffect, useState } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
EuiComboBox,
|
||||
|
@ -29,10 +29,12 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
import type { DownloadSource, FleetProxy } from '../../../../types';
|
||||
import { MAX_FLYOUT_WIDTH } from '../../../../constants';
|
||||
import { useBreadcrumbs, useStartServices } from '../../../../hooks';
|
||||
import { useBreadcrumbs, useFleetStatus, useStartServices } from '../../../../hooks';
|
||||
import { ProxyWarning } from '../fleet_proxies_table/proxy_warning';
|
||||
import { ExperimentalFeaturesService } from '../../../../services';
|
||||
|
||||
import { useDowloadSourceFlyoutForm } from './use_download_source_flyout_form';
|
||||
import { SSLFormSection } from './ssl_form_section';
|
||||
|
||||
export interface EditDownloadSourceFlyoutProps {
|
||||
downloadSource?: DownloadSource;
|
||||
|
@ -53,6 +55,59 @@ export const EditDownloadSourceFlyout: React.FunctionComponent<EditDownloadSourc
|
|||
() => proxies.map((proxy) => ({ value: proxy.id, label: proxy.name })),
|
||||
[proxies]
|
||||
);
|
||||
|
||||
const [isFirstLoad, setIsFirstLoad] = React.useState(true);
|
||||
const [secretsToggleState, setSecretsToggleState] = useState<'disabled' | true | false>(true);
|
||||
const useSecretsStorage = secretsToggleState === true;
|
||||
const [isConvertedToSecret, setIsConvertedToSecret] = React.useState({
|
||||
sslKey: false,
|
||||
});
|
||||
const { enableSSLSecrets } = ExperimentalFeaturesService.get();
|
||||
|
||||
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 output 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 });
|
||||
}
|
||||
}
|
||||
}, [
|
||||
useSecretsStorage,
|
||||
inputs.sslKeyInput,
|
||||
inputs.sslKeySecretInput,
|
||||
isFirstLoad,
|
||||
setIsFirstLoad,
|
||||
isConvertedToSecret,
|
||||
enableSSLSecrets,
|
||||
]);
|
||||
|
||||
const onToggleSecretAndClearValue = (secretEnabled: boolean) => {
|
||||
if (secretEnabled) {
|
||||
inputs.sslKeyInput.clear();
|
||||
} else {
|
||||
inputs.sslKeySecretInput.setValue('');
|
||||
}
|
||||
setIsConvertedToSecret({ sslKey: false });
|
||||
onToggleSecretStorage(secretEnabled);
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiFlyout onClose={onClose} maxWidth={MAX_FLYOUT_WIDTH}>
|
||||
<EuiFlyoutHeader hasBorder={true}>
|
||||
|
@ -184,6 +239,13 @@ export const EditDownloadSourceFlyout: React.FunctionComponent<EditDownloadSourc
|
|||
}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer size="m" />
|
||||
<SSLFormSection
|
||||
inputs={inputs}
|
||||
useSecretsStorage={enableSSLSecrets && useSecretsStorage}
|
||||
isConvertedToSecret={isConvertedToSecret.sslKey}
|
||||
onToggleSecretAndClearValue={onToggleSecretAndClearValue}
|
||||
/>
|
||||
</EuiForm>
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* 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, EuiCallOut, EuiSpacer, EuiPanel, EuiTitle } 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 type { DownlaodSourceFormInputsType } from './use_download_source_flyout_form';
|
||||
|
||||
interface Props {
|
||||
inputs: DownlaodSourceFormInputsType;
|
||||
useSecretsStorage: boolean;
|
||||
isConvertedToSecret: boolean;
|
||||
onToggleSecretAndClearValue: (secretEnabled: boolean) => void;
|
||||
}
|
||||
|
||||
export const SSLFormSection: React.FunctionComponent<Props> = (props) => {
|
||||
const { inputs, useSecretsStorage, isConvertedToSecret, onToggleSecretAndClearValue } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiPanel color="subdued" borderRadius="none" hasShadow={false}>
|
||||
<EuiTitle size="s">
|
||||
<h3 id="FleetEditOutputFlyoutKafkaAuthenticationTitle">
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.settings.editDownloadSourcesFlyout.sslAuthenticationTitle"
|
||||
defaultMessage="Authentication"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiCallOut
|
||||
title={i18n.translate('xpack.fleet.editDownloadSourcesFlyout.sslWarningCallout', {
|
||||
defaultMessage:
|
||||
'Invalid settings can prevent Elastic Agent from being able to upgrade. If this happens, you will need to provide valid credentials.',
|
||||
})}
|
||||
color="warning"
|
||||
iconType="warning"
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<MultiRowInput
|
||||
placeholder={i18n.translate(
|
||||
'xpack.fleet.settings.editDownloadSourcesFlyout.sslCertificateAuthoritiesInputPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Specify certificate authority',
|
||||
}
|
||||
)}
|
||||
label={i18n.translate(
|
||||
'xpack.fleet.settings.editDownloadSourcesFlyout.sslCertificateAuthoritiesInputLabel',
|
||||
{
|
||||
defaultMessage: 'Server SSL certificate authorities (optional)',
|
||||
}
|
||||
)}
|
||||
multiline={true}
|
||||
sortable={false}
|
||||
{...inputs.sslCertificateAuthoritiesInput.props}
|
||||
/>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.settings.editDownloadSourcesFlyout.sslCertificateInputLabel"
|
||||
defaultMessage="Client SSL certificate"
|
||||
/>
|
||||
}
|
||||
{...inputs.sslCertificateInput.formRowProps}
|
||||
>
|
||||
<EuiTextArea
|
||||
fullWidth
|
||||
rows={5}
|
||||
{...inputs.sslCertificateInput.props}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.fleet.settings.editDownloadSourcesFlyout.sslCertificateInputPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Specify ssl certificate',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
{!useSecretsStorage ? (
|
||||
<SecretFormRow
|
||||
fullWidth
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.settings.editDownloadSourcesFlyout.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.editDownloadSourcesFlyout.sslKeyInputPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Specify certificate key',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</SecretFormRow>
|
||||
) : (
|
||||
<SecretFormRow
|
||||
fullWidth
|
||||
title={i18n.translate(
|
||||
'xpack.fleet.settings.editDownloadSourcesFlyout.sslKeySecretInputTitle',
|
||||
{
|
||||
defaultMessage: 'Client SSL certificate key',
|
||||
}
|
||||
)}
|
||||
{...inputs.sslKeySecretInput.formRowProps}
|
||||
useSecretsStorage={useSecretsStorage}
|
||||
isConvertedToSecret={isConvertedToSecret}
|
||||
onToggleSecretStorage={onToggleSecretAndClearValue}
|
||||
cancelEdit={inputs.sslKeySecretInput.cancelEdit}
|
||||
>
|
||||
<EuiTextArea
|
||||
fullWidth
|
||||
rows={5}
|
||||
{...inputs.sslKeySecretInput.props}
|
||||
data-test-subj="sslKeySecretInput"
|
||||
placeholder={i18n.translate(
|
||||
'xpack.fleet.settings.editDownloadSourcesFlyout.sslKeySecretInputPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Specify certificate key',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</SecretFormRow>
|
||||
)}
|
||||
</EuiPanel>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -9,6 +9,7 @@ import { useCallback, useState } from 'react';
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { useSecretInput, useComboInput } from '../../../../hooks';
|
||||
import {
|
||||
sendPostDownloadSource,
|
||||
useInput,
|
||||
|
@ -20,8 +21,21 @@ import {
|
|||
import type { DownloadSource, PostDownloadSourceRequest } from '../../../../types';
|
||||
import { useConfirmModal } from '../../hooks/use_confirm_modal';
|
||||
|
||||
import type { DownloadSourceBase } from '../../../../../../../common/types';
|
||||
|
||||
import { confirmUpdate } from './confirm_update';
|
||||
|
||||
export interface DownlaodSourceFormInputsType {
|
||||
nameInput: ReturnType<typeof useInput>;
|
||||
defaultDownloadSourceInput: ReturnType<typeof useSwitchInput>;
|
||||
hostInput: ReturnType<typeof useInput>;
|
||||
proxyIdInput: ReturnType<typeof useInput>;
|
||||
sslCertificateInput: ReturnType<typeof useInput>;
|
||||
sslKeyInput: ReturnType<typeof useInput>;
|
||||
sslKeySecretInput: ReturnType<typeof useSecretInput>;
|
||||
sslCertificateAuthoritiesInput: ReturnType<typeof useComboInput>;
|
||||
}
|
||||
|
||||
export function useDowloadSourceFlyoutForm(onSuccess: () => void, downloadSource?: DownloadSource) {
|
||||
const authz = useAuthz();
|
||||
const [isLoading, setIsloading] = useState(false);
|
||||
|
@ -41,11 +55,34 @@ export function useDowloadSourceFlyoutForm(onSuccess: () => void, downloadSource
|
|||
|
||||
const proxyIdInput = useInput(downloadSource?.proxy_id ?? '', () => undefined, isEditDisabled);
|
||||
|
||||
const sslCertificateAuthoritiesInput = useComboInput(
|
||||
'sslCertificateAuthoritiesComboxBox',
|
||||
downloadSource?.ssl?.certificate_authorities ?? [],
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
const sslCertificateInput = useInput(
|
||||
downloadSource?.ssl?.certificate ?? '',
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
const sslKeyInput = useInput(downloadSource?.ssl?.key ?? '', undefined, undefined);
|
||||
|
||||
const sslKeySecretInput = useSecretInput(
|
||||
(downloadSource as DownloadSourceBase)?.secrets?.ssl?.key,
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
|
||||
const inputs = {
|
||||
nameInput,
|
||||
hostInput,
|
||||
defaultDownloadSourceInput,
|
||||
proxyIdInput,
|
||||
sslCertificateInput,
|
||||
sslKeyInput,
|
||||
sslCertificateAuthoritiesInput,
|
||||
sslKeySecretInput,
|
||||
};
|
||||
|
||||
const hasChanged = Object.values(inputs).some((input) => input.hasChanged);
|
||||
|
@ -54,8 +91,12 @@ export function useDowloadSourceFlyoutForm(onSuccess: () => void, downloadSource
|
|||
const nameInputValid = nameInput.validate();
|
||||
const hostValid = hostInput.validate();
|
||||
|
||||
return nameInputValid && hostValid;
|
||||
}, [nameInput, hostInput]);
|
||||
const sslCertificateValid = sslCertificateInput.validate();
|
||||
const sslKeyValid = sslKeyInput.validate();
|
||||
const sslKeySecretValid = sslKeySecretInput.validate();
|
||||
|
||||
return nameInputValid && hostValid && sslCertificateValid && sslKeyValid && sslKeySecretValid;
|
||||
}, [nameInput, hostInput, sslCertificateInput, sslKeyInput, sslKeySecretInput]);
|
||||
|
||||
const submit = useCallback(async () => {
|
||||
try {
|
||||
|
@ -69,6 +110,19 @@ export function useDowloadSourceFlyoutForm(onSuccess: () => void, downloadSource
|
|||
host: hostInput.value.trim(),
|
||||
is_default: defaultDownloadSourceInput.value,
|
||||
proxy_id: proxyIdInput.value || null,
|
||||
ssl: {
|
||||
certificate: sslCertificateInput.value,
|
||||
key: sslKeyInput.value || undefined,
|
||||
certificate_authorities: sslCertificateAuthoritiesInput.value.filter((val) => val !== ''),
|
||||
},
|
||||
...(!sslKeyInput.value &&
|
||||
sslKeySecretInput.value && {
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: sslKeySecretInput.value || undefined,
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
if (downloadSource) {
|
||||
|
@ -109,6 +163,10 @@ export function useDowloadSourceFlyoutForm(onSuccess: () => void, downloadSource
|
|||
notifications.toasts,
|
||||
onSuccess,
|
||||
proxyIdInput.value,
|
||||
sslCertificateAuthoritiesInput.value,
|
||||
sslCertificateInput.value,
|
||||
sslKeyInput.value,
|
||||
sslKeySecretInput.value,
|
||||
validate,
|
||||
]);
|
||||
|
||||
|
|
|
@ -7,37 +7,67 @@
|
|||
|
||||
import { savedObjectsClientMock } from '@kbn/core/server/mocks';
|
||||
|
||||
import { securityMock } from '@kbn/security-plugin/server/mocks';
|
||||
|
||||
import { DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE } from '../../constants';
|
||||
|
||||
import type { AgentPolicy, DownloadSource } from '../../types';
|
||||
import type { AgentPolicy } from '../../types';
|
||||
|
||||
import { getSourceUriForAgentPolicy } from './source_uri_utils';
|
||||
import { appContextService } from '../../services/app_context';
|
||||
|
||||
const soClientMock = savedObjectsClientMock.create();
|
||||
import { getDownloadSourceForAgentPolicy } from './source_uri_utils';
|
||||
|
||||
jest.mock('../download_source', () => {
|
||||
return {
|
||||
downloadSourceService: {
|
||||
getDefaultDownloadSourceId: async () => 'default-download-source-id',
|
||||
get: async (soClient: any, id: string): Promise<DownloadSource> => {
|
||||
if (id === 'test-ds-1') {
|
||||
return {
|
||||
id: 'test-ds-1',
|
||||
is_default: false,
|
||||
name: 'Test',
|
||||
host: 'http://custom-registry-test',
|
||||
};
|
||||
}
|
||||
return {
|
||||
id: 'default-download-source-id',
|
||||
jest.mock('../../services/app_context');
|
||||
const mockedAppContextService = appContextService as jest.Mocked<typeof appContextService>;
|
||||
mockedAppContextService.getSecuritySetup.mockImplementation(() => ({
|
||||
...securityMock.createSetup(),
|
||||
}));
|
||||
|
||||
function getMockedSoClient(options: { id?: string; sameName?: boolean } = {}) {
|
||||
const soClientMock = savedObjectsClientMock.create();
|
||||
|
||||
soClientMock.get.mockImplementation(async (type: string, id: string) => {
|
||||
switch (id) {
|
||||
case 'test-ds-1': {
|
||||
return mockDownloadSourceSO('test-ds-1', {
|
||||
is_default: false,
|
||||
name: 'Test',
|
||||
host: 'http://custom-registry-test',
|
||||
});
|
||||
}
|
||||
case 'default-download-source-id': {
|
||||
return mockDownloadSourceSO('default-download-source-id', {
|
||||
is_default: true,
|
||||
name: 'Default host',
|
||||
host: 'http://default-registry.co',
|
||||
};
|
||||
});
|
||||
}
|
||||
default:
|
||||
throw new Error('not found: ' + id);
|
||||
}
|
||||
});
|
||||
soClientMock.find.mockResolvedValue({
|
||||
saved_objects: [
|
||||
{
|
||||
id: 'default-download-source-id',
|
||||
is_default: true,
|
||||
attributes: {
|
||||
download_source_id: 'test-source-id',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
{
|
||||
id: 'test-ds-1',
|
||||
attributes: {
|
||||
download_source_id: 'test-ds-1',
|
||||
},
|
||||
},
|
||||
],
|
||||
} as any);
|
||||
|
||||
mockedAppContextService.getInternalUserSOClient.mockReturnValue(soClientMock);
|
||||
|
||||
return soClientMock;
|
||||
}
|
||||
|
||||
function mockDownloadSourceSO(id: string, attributes: any = {}) {
|
||||
return {
|
||||
|
@ -51,47 +81,10 @@ function mockDownloadSourceSO(id: string, attributes: any = {}) {
|
|||
};
|
||||
}
|
||||
describe('helpers', () => {
|
||||
beforeEach(() => {
|
||||
soClientMock.get.mockImplementation(async (type: string, id: string) => {
|
||||
switch (id) {
|
||||
case 'test-ds-1': {
|
||||
return mockDownloadSourceSO('test-ds-1', {
|
||||
is_default: false,
|
||||
name: 'Test',
|
||||
host: 'http://custom-registry-test',
|
||||
});
|
||||
}
|
||||
case 'default-download-source-id': {
|
||||
return mockDownloadSourceSO('default-download-source-id', {
|
||||
is_default: true,
|
||||
name: 'Default host',
|
||||
host: 'http://default-registry.co',
|
||||
});
|
||||
}
|
||||
default:
|
||||
throw new Error('not found: ' + id);
|
||||
}
|
||||
});
|
||||
soClientMock.find.mockResolvedValue({
|
||||
saved_objects: [
|
||||
{
|
||||
id: 'default-download-source-id',
|
||||
is_default: true,
|
||||
attributes: {
|
||||
download_source_id: 'test-source-id',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'test-ds-1',
|
||||
attributes: {
|
||||
download_source_id: 'test-ds-1',
|
||||
},
|
||||
},
|
||||
],
|
||||
} as any);
|
||||
});
|
||||
describe('getSourceUriForAgentPolicy', () => {
|
||||
it('should return the source_uri set on an agent policy ', async () => {
|
||||
beforeEach(() => {});
|
||||
describe('getDownloadSourceForAgentPolicy', () => {
|
||||
it('should return the dowload source object set on an agent policy ', async () => {
|
||||
const soClient = getMockedSoClient();
|
||||
const agentPolicy: AgentPolicy = {
|
||||
id: 'agent-policy-id',
|
||||
status: 'active',
|
||||
|
@ -106,11 +99,16 @@ describe('helpers', () => {
|
|||
is_protected: false,
|
||||
};
|
||||
|
||||
expect(await getSourceUriForAgentPolicy(soClientMock, agentPolicy)).toEqual({
|
||||
expect(await getDownloadSourceForAgentPolicy(soClient, agentPolicy)).toEqual({
|
||||
host: 'http://custom-registry-test',
|
||||
id: 'test-ds-1',
|
||||
is_default: false,
|
||||
name: 'Test',
|
||||
});
|
||||
});
|
||||
it('should return the default source_uri if there is none set on the agent policy ', async () => {
|
||||
|
||||
it('should return the default download source object if there is none set on the agent policy ', async () => {
|
||||
const soClient = getMockedSoClient();
|
||||
const agentPolicy: AgentPolicy = {
|
||||
id: 'agent-policy-id',
|
||||
status: 'active',
|
||||
|
@ -124,8 +122,11 @@ describe('helpers', () => {
|
|||
is_protected: false,
|
||||
};
|
||||
|
||||
expect(await getSourceUriForAgentPolicy(soClientMock, agentPolicy)).toEqual({
|
||||
expect(await getDownloadSourceForAgentPolicy(soClient, agentPolicy)).toEqual({
|
||||
host: 'http://default-registry.co',
|
||||
id: 'default-download-source-id',
|
||||
is_default: true,
|
||||
name: 'Default host',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,13 +8,13 @@
|
|||
import type { SavedObjectsClientContract } from '@kbn/core/server';
|
||||
|
||||
import { downloadSourceService } from '../../services';
|
||||
import type { AgentPolicy } from '../../types';
|
||||
import type { AgentPolicy, DownloadSource } from '../../types';
|
||||
import { FleetError, DownloadSourceNotFound } from '../../errors';
|
||||
|
||||
export const getSourceUriForAgentPolicy = async (
|
||||
export const getDownloadSourceForAgentPolicy = async (
|
||||
soClient: SavedObjectsClientContract,
|
||||
agentPolicy: AgentPolicy
|
||||
) => {
|
||||
): Promise<DownloadSource> => {
|
||||
const defaultDownloadSourceId = await downloadSourceService.getDefaultDownloadSourceId(soClient);
|
||||
|
||||
if (!defaultDownloadSourceId) {
|
||||
|
@ -25,5 +25,5 @@ export const getSourceUriForAgentPolicy = async (
|
|||
if (!downloadSource) {
|
||||
throw new DownloadSourceNotFound(`Download source host not found ${downloadSourceId}`);
|
||||
}
|
||||
return { host: downloadSource.host, proxy_id: downloadSource.proxy_id };
|
||||
return downloadSource;
|
||||
};
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
import type { RequestHandler } from '@kbn/core/server';
|
||||
import type { TypeOf } from '@kbn/config-schema';
|
||||
|
||||
import Boom from '@hapi/boom';
|
||||
|
||||
import type {
|
||||
GetOneDownloadSourcesRequestSchema,
|
||||
PutDownloadSourcesRequestSchema,
|
||||
|
@ -19,10 +21,17 @@ import type {
|
|||
DeleteDownloadSourceResponse,
|
||||
PutDownloadSourceResponse,
|
||||
GetDownloadSourceResponse,
|
||||
DownloadSource,
|
||||
} from '../../../common/types';
|
||||
import { downloadSourceService } from '../../services/download_source';
|
||||
import { agentPolicyService } from '../../services';
|
||||
|
||||
function ensureNoDuplicateSecrets(downloadSource: Partial<DownloadSource>) {
|
||||
if (downloadSource.ssl?.key && downloadSource.secrets?.ssl?.key) {
|
||||
throw Boom.badRequest('Cannot specify both ssl.key and secrets.ssl.key');
|
||||
}
|
||||
}
|
||||
|
||||
export const getDownloadSourcesHandler: RequestHandler = async (context, request, response) => {
|
||||
const soClient = (await context.core).savedObjects.client;
|
||||
const downloadSources = await downloadSourceService.list(soClient);
|
||||
|
@ -52,7 +61,7 @@ export const getOneDownloadSourcesHandler: RequestHandler<
|
|||
} catch (error) {
|
||||
if (error.isBoom && error.output.statusCode === 404) {
|
||||
return response.notFound({
|
||||
body: { message: `Download source ${request.params.sourceId} not found` },
|
||||
body: { message: `Agent binary source ${request.params.sourceId} not found` },
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -68,15 +77,16 @@ export const putDownloadSourcesHandler: RequestHandler<
|
|||
const coreContext = await context.core;
|
||||
const soClient = coreContext.savedObjects.client;
|
||||
const esClient = coreContext.elasticsearch.client.asInternalUser;
|
||||
ensureNoDuplicateSecrets(request.body);
|
||||
|
||||
try {
|
||||
await downloadSourceService.update(soClient, request.params.sourceId, request.body);
|
||||
await downloadSourceService.update(soClient, esClient, request.params.sourceId, request.body);
|
||||
const downloadSource = await downloadSourceService.get(soClient, request.params.sourceId);
|
||||
if (downloadSource.is_default) {
|
||||
await agentPolicyService.bumpAllAgentPolicies(esClient);
|
||||
} else {
|
||||
await agentPolicyService.bumpAllAgentPoliciesForDownloadSource(esClient, downloadSource.id);
|
||||
}
|
||||
|
||||
const body: PutDownloadSourceResponse = {
|
||||
item: downloadSource,
|
||||
};
|
||||
|
@ -102,11 +112,13 @@ export const postDownloadSourcesHandler: RequestHandler<
|
|||
const soClient = coreContext.savedObjects.client;
|
||||
const esClient = coreContext.elasticsearch.client.asInternalUser;
|
||||
const { id, ...data } = request.body;
|
||||
const downloadSource = await downloadSourceService.create(soClient, data, { id });
|
||||
|
||||
ensureNoDuplicateSecrets(data);
|
||||
|
||||
const downloadSource = await downloadSourceService.create(soClient, esClient, data, { id });
|
||||
if (downloadSource.is_default) {
|
||||
await agentPolicyService.bumpAllAgentPolicies(esClient);
|
||||
}
|
||||
|
||||
const body: GetOneDownloadSourceResponse = {
|
||||
item: downloadSource,
|
||||
};
|
||||
|
@ -129,7 +141,7 @@ export const deleteDownloadSourcesHandler: RequestHandler<
|
|||
} catch (error) {
|
||||
if (error.isBoom && error.output.statusCode === 404) {
|
||||
return response.notFound({
|
||||
body: { message: `Donwload source ${request.params.sourceId} not found` },
|
||||
body: { message: `Agent binary source ${request.params.sourceId} not found` },
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1129,6 +1129,7 @@ export const getSavedObjectTypes = (
|
|||
importableAndExportable: false,
|
||||
},
|
||||
mappings: {
|
||||
dynamic: false,
|
||||
properties: {
|
||||
source_id: { type: 'keyword', index: false },
|
||||
name: { type: 'keyword' },
|
||||
|
@ -1137,6 +1138,16 @@ export const getSavedObjectTypes = (
|
|||
proxy_id: { type: 'keyword' },
|
||||
},
|
||||
},
|
||||
modelVersions: {
|
||||
'1': {
|
||||
changes: [
|
||||
{
|
||||
type: 'mappings_addition',
|
||||
addedMappings: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
[FLEET_SERVER_HOST_SAVED_OBJECT_TYPE]: {
|
||||
name: FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
|
@ -1297,4 +1308,10 @@ export function registerEncryptedSavedObjects(
|
|||
// enforceRandomId allows to create an SO with an arbitrary id
|
||||
enforceRandomId: false,
|
||||
});
|
||||
encryptedSavedObjects.registerType({
|
||||
type: DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
|
||||
attributesToEncrypt: new Set([{ key: 'ssl', dangerouslyExposeValue: true }]),
|
||||
// enforceRandomId allows to create an SO with an arbitrary id
|
||||
enforceRandomId: false,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -20,9 +20,11 @@ import { getFleetServerHostsForAgentPolicy } from '../fleet_server_host';
|
|||
|
||||
import {
|
||||
generateFleetConfig,
|
||||
getBinarySourceSettings,
|
||||
getFullAgentPolicy,
|
||||
getFullMonitoringSettings,
|
||||
transformOutputToFullPolicyOutput,
|
||||
generateFleetServerOutputSSLConfig,
|
||||
} from './full_agent_policy';
|
||||
import { getMonitoringPermissions } from './monitoring_permissions';
|
||||
|
||||
|
@ -126,6 +128,23 @@ jest.mock('../download_source', () => {
|
|||
is_default: false,
|
||||
name: 'Test',
|
||||
host: 'http://custom-registry-test',
|
||||
ssl: {
|
||||
certificate: 'cert',
|
||||
certificate_authorities: ['ca'],
|
||||
key: 'KEY1',
|
||||
},
|
||||
};
|
||||
} else if (id === 'test-ds-secrets') {
|
||||
return {
|
||||
id: 'test-ds-1',
|
||||
is_default: false,
|
||||
name: 'Test',
|
||||
host: 'http://custom-registry-test',
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: 'KEY1',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
|
@ -685,7 +704,7 @@ describe('getFullAgentPolicy', () => {
|
|||
expect(agentPolicy).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return the sourceURI from the agent policy', async () => {
|
||||
it('should return agent binary sourceURI and ssl options from the agent policy', async () => {
|
||||
mockAgentPolicy({
|
||||
namespace: 'default',
|
||||
revision: 1,
|
||||
|
@ -710,6 +729,53 @@ describe('getFullAgentPolicy', () => {
|
|||
agent: {
|
||||
download: {
|
||||
sourceURI: 'http://custom-registry-test',
|
||||
ssl: {
|
||||
certificate: 'cert',
|
||||
certificate_authorities: ['ca'],
|
||||
key: 'KEY1',
|
||||
},
|
||||
},
|
||||
monitoring: {
|
||||
namespace: 'default',
|
||||
use_output: 'default',
|
||||
enabled: true,
|
||||
logs: false,
|
||||
metrics: true,
|
||||
traces: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
it('should return agent binary with secrets if there are any present', async () => {
|
||||
mockAgentPolicy({
|
||||
namespace: 'default',
|
||||
revision: 1,
|
||||
monitoring_enabled: ['metrics'],
|
||||
download_source_id: 'test-ds-secrets',
|
||||
});
|
||||
const agentPolicy = await getFullAgentPolicy(savedObjectsClientMock.create(), 'agent-policy');
|
||||
|
||||
expect(agentPolicy).toMatchObject({
|
||||
id: 'agent-policy',
|
||||
outputs: {
|
||||
default: {
|
||||
type: 'elasticsearch',
|
||||
hosts: ['http://127.0.0.1:9201'],
|
||||
},
|
||||
},
|
||||
inputs: [],
|
||||
revision: 1,
|
||||
fleet: {
|
||||
hosts: ['http://fleetserver:8220'],
|
||||
},
|
||||
agent: {
|
||||
download: {
|
||||
sourceURI: 'http://custom-registry-test',
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: 'KEY1',
|
||||
},
|
||||
},
|
||||
},
|
||||
monitoring: {
|
||||
namespace: 'default',
|
||||
|
@ -1591,20 +1657,14 @@ describe('generateFleetConfig', () => {
|
|||
outputs
|
||||
);
|
||||
|
||||
expect(res).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"hosts": Array [
|
||||
"https://test.fr",
|
||||
],
|
||||
"ssl": Object {
|
||||
"certificate": "my-cert",
|
||||
"certificate_authorities": Array [
|
||||
"/tmp/ssl/ca.crt",
|
||||
],
|
||||
"key": "my-key",
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(res).toEqual({
|
||||
hosts: ['https://test.fr'],
|
||||
ssl: {
|
||||
certificate: 'my-cert',
|
||||
certificate_authorities: ['/tmp/ssl/ca.crt'],
|
||||
key: 'my-key',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate ssl config when a default remote_elasticsearch output has ssl options', () => {
|
||||
|
@ -1641,20 +1701,14 @@ describe('generateFleetConfig', () => {
|
|||
outputs
|
||||
);
|
||||
|
||||
expect(res).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"hosts": Array [
|
||||
"https://test.fr",
|
||||
],
|
||||
"ssl": Object {
|
||||
"certificate": "my-cert",
|
||||
"certificate_authorities": Array [
|
||||
"/tmp/ssl/ca.crt",
|
||||
],
|
||||
"key": "my-key",
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(res).toEqual({
|
||||
hosts: ['https://test.fr'],
|
||||
ssl: {
|
||||
certificate: 'my-cert',
|
||||
certificate_authorities: ['/tmp/ssl/ca.crt'],
|
||||
key: 'my-key',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate ssl config when a ES custom output has ssl options', () => {
|
||||
|
@ -1679,7 +1733,7 @@ describe('generateFleetConfig', () => {
|
|||
},
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: 'my-key',
|
||||
key: { id: 'my-key' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1695,24 +1749,18 @@ describe('generateFleetConfig', () => {
|
|||
outputs
|
||||
);
|
||||
|
||||
expect(res).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"hosts": Array [
|
||||
"https://test.fr",
|
||||
],
|
||||
"secrets": Object {
|
||||
"ssl": Object {
|
||||
"key": "my-key",
|
||||
},
|
||||
expect(res).toEqual({
|
||||
hosts: ['https://test.fr'],
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: { id: 'my-key' },
|
||||
},
|
||||
"ssl": Object {
|
||||
"certificate": "my-cert",
|
||||
"certificate_authorities": Array [
|
||||
"/tmp/ssl/ca.crt",
|
||||
],
|
||||
},
|
||||
}
|
||||
`);
|
||||
},
|
||||
ssl: {
|
||||
certificate: 'my-cert',
|
||||
certificate_authorities: ['/tmp/ssl/ca.crt'],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate ssl config when a remote_elasticsearch custom output has ssl options', () => {
|
||||
|
@ -1737,7 +1785,7 @@ describe('generateFleetConfig', () => {
|
|||
},
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: 'my-key',
|
||||
key: { id: 'my-key' },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1753,23 +1801,318 @@ describe('generateFleetConfig', () => {
|
|||
outputs
|
||||
);
|
||||
|
||||
expect(res).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"hosts": Array [
|
||||
"https://test.fr",
|
||||
],
|
||||
"secrets": Object {
|
||||
"ssl": Object {
|
||||
"key": "my-key",
|
||||
expect(res).toEqual({
|
||||
hosts: ['https://test.fr'],
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: {
|
||||
id: 'my-key',
|
||||
},
|
||||
},
|
||||
"ssl": Object {
|
||||
"certificate": "my-cert",
|
||||
"certificate_authorities": Array [
|
||||
"/tmp/ssl/ca.crt",
|
||||
],
|
||||
},
|
||||
ssl: {
|
||||
certificate: 'my-cert',
|
||||
certificate_authorities: ['/tmp/ssl/ca.crt'],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should use secrets key if both keys are present', () => {
|
||||
const outputs = [
|
||||
{
|
||||
id: 'output-1',
|
||||
name: 'Output 1',
|
||||
type: 'elasticsearch',
|
||||
is_default: true,
|
||||
hosts: ['http://test.fr:9200'],
|
||||
},
|
||||
{
|
||||
id: 'output-2',
|
||||
name: 'Output 2',
|
||||
type: 'remote_elasticsearch',
|
||||
is_default_monitoring: false,
|
||||
hosts: ['http://test.fr:9200'],
|
||||
is_default: false,
|
||||
ssl: {
|
||||
certificate_authorities: ['/tmp/ssl/ca.crt'],
|
||||
certificate: 'my-cert',
|
||||
key: { id: 'my-key' },
|
||||
},
|
||||
}
|
||||
`);
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: { id: 'my-secret-key' },
|
||||
},
|
||||
},
|
||||
},
|
||||
] as any;
|
||||
|
||||
const agentPolicyWithCustomOutput = { ...agentPolicy, data_output_id: 'output-2' };
|
||||
const res = generateFleetConfig(
|
||||
agentPolicyWithCustomOutput,
|
||||
{
|
||||
host_urls: ['https://test.fr'],
|
||||
} as any,
|
||||
[],
|
||||
outputs
|
||||
);
|
||||
|
||||
expect(res).toEqual({
|
||||
hosts: ['https://test.fr'],
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: {
|
||||
id: 'my-secret-key',
|
||||
},
|
||||
},
|
||||
},
|
||||
ssl: {
|
||||
certificate: 'my-cert',
|
||||
certificate_authorities: ['/tmp/ssl/ca.crt'],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateFleetServerOutputSSLConfig', () => {
|
||||
const baseFleetServerHost = {
|
||||
name: 'default Fleet Server',
|
||||
id: '93f74c0-e876-11ea-b7d3-8b2acec6f75c',
|
||||
is_default: true,
|
||||
host_urls: ['http://fleetserver:8220'],
|
||||
is_preconfigured: false,
|
||||
} as any;
|
||||
|
||||
it('should return undefined if no fleetServerHost is passed', () => {
|
||||
const res = generateFleetServerOutputSSLConfig(undefined);
|
||||
expect(res).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('should return undefined if fleetServerHost has no ssl and no secrets', () => {
|
||||
const res = generateFleetServerOutputSSLConfig(baseFleetServerHost);
|
||||
expect(res).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('should generate a bootstrap output if there are ES ssl fields', () => {
|
||||
const fleetServerHost = {
|
||||
...baseFleetServerHost,
|
||||
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',
|
||||
},
|
||||
};
|
||||
const res = generateFleetServerOutputSSLConfig(fleetServerHost);
|
||||
expect(res).toEqual({
|
||||
'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',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate a bootstrap output if there are ES secrets fields', () => {
|
||||
const fleetServerHost = {
|
||||
...baseFleetServerHost,
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: 'my-key',
|
||||
es_key: 'my-es-key',
|
||||
},
|
||||
},
|
||||
};
|
||||
const res = generateFleetServerOutputSSLConfig(fleetServerHost);
|
||||
expect(res).toEqual({
|
||||
'fleetserver-output-93f74c0-e876-11ea-b7d3-8b2acec6f75c': {
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: 'my-es-key',
|
||||
},
|
||||
},
|
||||
type: 'elasticsearch',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate a bootstrap output if there are both secrets and ES ssl fields', () => {
|
||||
const fleetServerHost = {
|
||||
...baseFleetServerHost,
|
||||
ssl: {
|
||||
es_certificate_authorities: ['/tmp/ssl/es-ca.crt'],
|
||||
es_certificate: 'my-es-cert',
|
||||
},
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: { id: 'my-key' },
|
||||
es_key: { id: 'my-es-key' },
|
||||
},
|
||||
},
|
||||
};
|
||||
const res = generateFleetServerOutputSSLConfig(fleetServerHost);
|
||||
expect(res).toEqual({
|
||||
'fleetserver-output-93f74c0-e876-11ea-b7d3-8b2acec6f75c': {
|
||||
ssl: {
|
||||
certificate_authorities: ['/tmp/ssl/es-ca.crt'],
|
||||
certificate: 'my-es-cert',
|
||||
},
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: { id: 'my-es-key' },
|
||||
},
|
||||
},
|
||||
type: 'elasticsearch',
|
||||
},
|
||||
});
|
||||
});
|
||||
it('should use secrets key if the key is present in both ways', () => {
|
||||
const fleetServerHost = {
|
||||
...baseFleetServerHost,
|
||||
ssl: {
|
||||
es_certificate_authorities: ['/tmp/ssl/es-ca.crt'],
|
||||
es_certificate: 'my-es-cert',
|
||||
es_key: { id: 'my-es-key' },
|
||||
},
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: { id: 'my-key' },
|
||||
es_key: { id: 'my-secret-es-key' },
|
||||
},
|
||||
},
|
||||
};
|
||||
const res = generateFleetServerOutputSSLConfig(fleetServerHost);
|
||||
expect(res).toEqual({
|
||||
'fleetserver-output-93f74c0-e876-11ea-b7d3-8b2acec6f75c': {
|
||||
ssl: {
|
||||
certificate_authorities: ['/tmp/ssl/es-ca.crt'],
|
||||
certificate: 'my-es-cert',
|
||||
},
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: { id: 'my-secret-es-key' },
|
||||
},
|
||||
},
|
||||
type: 'elasticsearch',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getBinarySourceSettings', () => {
|
||||
const downloadSource = {
|
||||
id: 'test-ds-1',
|
||||
is_default: false,
|
||||
name: 'Test',
|
||||
host: 'http://custom-registry-test',
|
||||
} as any;
|
||||
|
||||
it('should return sourceURI for agent download config', () => {
|
||||
expect(getBinarySourceSettings(downloadSource, null)).toEqual({
|
||||
sourceURI: 'http://custom-registry-test',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return agent download config with ssl options if present', () => {
|
||||
const downloadSourceSSL = {
|
||||
...downloadSource,
|
||||
ssl: {
|
||||
certificate: 'cert',
|
||||
certificate_authorities: ['ca'],
|
||||
key: 'KEY1',
|
||||
},
|
||||
};
|
||||
expect(getBinarySourceSettings(downloadSourceSSL, null)).toEqual({
|
||||
sourceURI: 'http://custom-registry-test',
|
||||
ssl: {
|
||||
certificate: 'cert',
|
||||
certificate_authorities: ['ca'],
|
||||
key: 'KEY1',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return agent download config when there is a proxy', () => {
|
||||
expect(getBinarySourceSettings(downloadSource, 'http://proxy_uri.it')).toEqual({
|
||||
proxy_url: 'http://proxy_uri.it',
|
||||
sourceURI: 'http://custom-registry-test',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return agent download config with secrets if present', () => {
|
||||
const downloadSourceSecrets = {
|
||||
...downloadSource,
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: { id: 'keyid' },
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(getBinarySourceSettings(downloadSourceSecrets, null)).toEqual({
|
||||
sourceURI: 'http://custom-registry-test',
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: { id: 'keyid' },
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return agent download config with secrets and ssl if present', () => {
|
||||
const downloadSourceSecrets = {
|
||||
...downloadSource,
|
||||
ssl: {
|
||||
certificate: 'cert',
|
||||
certificate_authorities: ['ca'],
|
||||
},
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: { id: 'keyid' },
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(getBinarySourceSettings(downloadSourceSecrets, null)).toEqual({
|
||||
sourceURI: 'http://custom-registry-test',
|
||||
ssl: {
|
||||
certificate: 'cert',
|
||||
certificate_authorities: ['ca'],
|
||||
},
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: { id: 'keyid' },
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
it('should return agent download config using secrets key if both keys are present', () => {
|
||||
const downloadSourceSecrets = {
|
||||
...downloadSource,
|
||||
ssl: {
|
||||
certificate: 'cert',
|
||||
certificate_authorities: ['ca'],
|
||||
key: { id: 'keyid' },
|
||||
},
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: { id: 'secretkeyid' },
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(getBinarySourceSettings(downloadSourceSecrets, null)).toEqual({
|
||||
sourceURI: 'http://custom-registry-test',
|
||||
ssl: {
|
||||
certificate: 'cert',
|
||||
certificate_authorities: ['ca'],
|
||||
},
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: { id: 'secretkeyid' },
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -28,6 +28,8 @@ import type {
|
|||
AgentPolicy,
|
||||
} from '../../types';
|
||||
import type {
|
||||
DownloadSource,
|
||||
FullAgentPolicyDownload,
|
||||
FullAgentPolicyInput,
|
||||
FullAgentPolicyMonitoring,
|
||||
FullAgentPolicyOutputPermissions,
|
||||
|
@ -45,7 +47,11 @@ import { getPackageInfo } from '../epm/packages';
|
|||
import { pkgToPkgKey, splitPkgKey } from '../epm/registry';
|
||||
import { appContextService } from '../app_context';
|
||||
|
||||
import { getFleetServerHostsSecretReferences, getOutputSecretReferences } from '../secrets';
|
||||
import {
|
||||
getFleetServerHostsSecretReferences,
|
||||
getOutputSecretReferences,
|
||||
getDownloadSourceSecretReferences,
|
||||
} from '../secrets';
|
||||
|
||||
import { getMonitoringPermissions } from './monitoring_permissions';
|
||||
import { storedPackagePoliciesToAgentInputs } from '.';
|
||||
|
@ -90,7 +96,7 @@ export async function getFullAgentPolicy(
|
|||
dataOutput,
|
||||
fleetServerHost,
|
||||
monitoringOutput,
|
||||
downloadSourceUri,
|
||||
downloadSource,
|
||||
downloadSourceProxyUri,
|
||||
} = await fetchRelatedSavedObjects(soClient, agentPolicy);
|
||||
// Build up an in-memory object for looking up Package Info, so we don't have
|
||||
|
@ -159,7 +165,9 @@ export async function getFullAgentPolicy(
|
|||
const fleetserverHostSecretReferences = fleetServerHost
|
||||
? getFleetServerHostsSecretReferences(fleetServerHost)
|
||||
: [];
|
||||
|
||||
const downloadSourceSecretReferences = downloadSource
|
||||
? getDownloadSourceSecretReferences(downloadSource)
|
||||
: [];
|
||||
const packagePolicySecretReferences = (agentPolicy?.package_policies || []).flatMap(
|
||||
(policy) => policy.secret_references || []
|
||||
);
|
||||
|
@ -181,14 +189,12 @@ export async function getFullAgentPolicy(
|
|||
secret_references: [
|
||||
...outputSecretReferences,
|
||||
...fleetserverHostSecretReferences,
|
||||
...downloadSourceSecretReferences,
|
||||
...packagePolicySecretReferences,
|
||||
],
|
||||
revision: agentPolicy.revision,
|
||||
agent: {
|
||||
download: {
|
||||
sourceURI: downloadSourceUri,
|
||||
...(downloadSourceProxyUri ? { proxy_url: downloadSourceProxyUri } : {}),
|
||||
},
|
||||
download: getBinarySourceSettings(downloadSource, downloadSourceProxyUri),
|
||||
monitoring: getFullMonitoringSettings(agentPolicy, monitoringOutput),
|
||||
features,
|
||||
protection: {
|
||||
|
@ -377,14 +383,13 @@ export function generateFleetConfig(
|
|||
}),
|
||||
};
|
||||
}
|
||||
|
||||
// if both ssl.es_key and secrets.ssl.es_key are present, prefer the secrets'
|
||||
if (output?.secrets) {
|
||||
config.secrets = {
|
||||
ssl: {
|
||||
...(output.secrets?.ssl?.key &&
|
||||
!output?.ssl?.key && {
|
||||
key: output.secrets.ssl.key,
|
||||
}),
|
||||
...(output.secrets?.ssl?.key && {
|
||||
key: output.secrets.ssl.key,
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -440,13 +445,12 @@ function generateSSLConfigForFleetServerInput(fleetServerHost: FleetServerHost)
|
|||
}),
|
||||
};
|
||||
}
|
||||
|
||||
// if both ssl.key and secrets.ssl.key are present, prefer the secrets'
|
||||
if (fleetServerHost?.secrets) {
|
||||
inputConfig.secrets = {
|
||||
...(fleetServerHost?.secrets?.ssl?.key &&
|
||||
!fleetServerHost?.ssl?.key && {
|
||||
ssl: { key: fleetServerHost.secrets?.ssl?.key },
|
||||
}),
|
||||
...(fleetServerHost?.secrets?.ssl?.key && {
|
||||
ssl: { key: fleetServerHost.secrets?.ssl?.key },
|
||||
}),
|
||||
};
|
||||
}
|
||||
return inputConfig;
|
||||
|
@ -633,7 +637,7 @@ export function transformOutputToFullPolicyOutput(
|
|||
// 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):
|
||||
export function generateFleetServerOutputSSLConfig(fleetServerHost: FleetServerHost | undefined):
|
||||
| {
|
||||
[key: string]: FullAgentPolicyOutput;
|
||||
}
|
||||
|
@ -655,12 +659,12 @@ function generateFleetServerOutputSSLConfig(fleetServerHost: FleetServerHost | u
|
|||
}),
|
||||
};
|
||||
}
|
||||
// if both ssl.es_key and secrets.ssl.es_key are present, prefer the secrets'
|
||||
if (fleetServerHost?.secrets) {
|
||||
outputConfig.secrets = {
|
||||
...(fleetServerHost?.secrets?.ssl?.es_key &&
|
||||
!fleetServerHost?.ssl?.es_key && {
|
||||
ssl: { key: fleetServerHost.secrets?.ssl?.es_key },
|
||||
}),
|
||||
...(fleetServerHost?.secrets?.ssl?.es_key && {
|
||||
ssl: { key: fleetServerHost.secrets?.ssl?.es_key },
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -794,3 +798,38 @@ function buildShipperQueueData(shipper: ShipperOutput) {
|
|||
};
|
||||
}
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
|
||||
export function getBinarySourceSettings(
|
||||
downloadSource: DownloadSource,
|
||||
downloadSourceProxyUri: string | null
|
||||
) {
|
||||
const config: FullAgentPolicyDownload = {
|
||||
sourceURI: downloadSource.host,
|
||||
...(downloadSourceProxyUri ? { proxy_url: downloadSourceProxyUri } : {}),
|
||||
};
|
||||
if (downloadSource?.ssl) {
|
||||
config.ssl = {
|
||||
...(downloadSource.ssl?.certificate_authorities && {
|
||||
certificate_authorities: downloadSource.ssl.certificate_authorities,
|
||||
}),
|
||||
...(downloadSource.ssl?.certificate && {
|
||||
certificate: downloadSource.ssl.certificate,
|
||||
}),
|
||||
...(downloadSource.ssl?.key &&
|
||||
!downloadSource?.secrets?.ssl?.key && {
|
||||
key: downloadSource.ssl.key,
|
||||
}),
|
||||
};
|
||||
}
|
||||
// if both ssl.es_key and secrets.ssl.key are present, prefer the secrets'
|
||||
if (downloadSource?.secrets) {
|
||||
config.secrets = {
|
||||
ssl: {
|
||||
...(downloadSource.secrets?.ssl?.key && {
|
||||
key: downloadSource.secrets.ssl.key,
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import { uniq } from 'lodash';
|
|||
import type { AgentPolicy } from '../../types';
|
||||
import { outputService } from '../output';
|
||||
|
||||
import { getSourceUriForAgentPolicy } from '../../routes/agent/source_uri_utils';
|
||||
import { getDownloadSourceForAgentPolicy } from '../../routes/agent/source_uri_utils';
|
||||
|
||||
import { getFleetServerHostsForAgentPolicy } from '../fleet_server_host';
|
||||
import { appContextService } from '../app_context';
|
||||
|
@ -46,18 +46,18 @@ export async function fetchRelatedSavedObjects(
|
|||
}, []),
|
||||
]);
|
||||
|
||||
const [outputs, { host: downloadSourceUri, proxy_id: downloadSourceProxyId }, fleetServerHosts] =
|
||||
await Promise.all([
|
||||
outputService.bulkGet(outputIds, { ignoreNotFound: true }),
|
||||
getSourceUriForAgentPolicy(soClient, agentPolicy),
|
||||
getFleetServerHostsForAgentPolicy(soClient, agentPolicy).catch((err) => {
|
||||
appContextService
|
||||
.getLogger()
|
||||
?.warn(`Unable to get fleet server hosts for policy ${agentPolicy?.id}: ${err.message}`);
|
||||
const [outputs, downloadSource, fleetServerHosts] = await Promise.all([
|
||||
outputService.bulkGet(outputIds, { ignoreNotFound: true }),
|
||||
getDownloadSourceForAgentPolicy(soClient, agentPolicy),
|
||||
getFleetServerHostsForAgentPolicy(soClient, agentPolicy).catch((err) => {
|
||||
appContextService
|
||||
.getLogger()
|
||||
?.warn(`Unable to get fleet server hosts for policy ${agentPolicy?.id}: ${err.message}`);
|
||||
|
||||
return undefined;
|
||||
}),
|
||||
]);
|
||||
return undefined;
|
||||
}),
|
||||
]);
|
||||
const { proxy_id: downloadSourceProxyId } = downloadSource;
|
||||
|
||||
const dataOutput = outputs.find((output) => output.id === dataOutputId);
|
||||
if (!dataOutput) {
|
||||
|
@ -92,7 +92,7 @@ export async function fetchRelatedSavedObjects(
|
|||
proxies,
|
||||
dataOutput,
|
||||
monitoringOutput,
|
||||
downloadSourceUri,
|
||||
downloadSource,
|
||||
downloadSourceProxyUri,
|
||||
fleetServerHost: fleetServerHosts,
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { savedObjectsClientMock } from '@kbn/core/server/mocks';
|
||||
import { savedObjectsClientMock, elasticsearchServiceMock } from '@kbn/core/server/mocks';
|
||||
|
||||
import { securityMock } from '@kbn/security-plugin/server/mocks';
|
||||
import { loggerMock } from '@kbn/logging-mocks';
|
||||
|
@ -140,19 +140,29 @@ describe('Download Service', () => {
|
|||
beforeEach(() => {
|
||||
mockedLogger = loggerMock.create();
|
||||
mockedAppContextService.getLogger.mockReturnValue(mockedLogger);
|
||||
jest
|
||||
.mocked(appContextService.getExperimentalFeatures)
|
||||
.mockReturnValue({ useSpaceAwareness: true } as any);
|
||||
mockedAppContextService.getEncryptedSavedObjectsSetup.mockReturnValue({
|
||||
canEncrypt: true,
|
||||
} as any);
|
||||
});
|
||||
afterEach(() => {
|
||||
mockedAgentPolicyService.list.mockClear();
|
||||
mockedAgentPolicyService.hasAPMIntegration.mockClear();
|
||||
mockedAgentPolicyService.removeDefaultSourceFromAll.mockReset();
|
||||
mockedAppContextService.getInternalUserSOClient.mockReset();
|
||||
mockedAppContextService.getEncryptedSavedObjectsSetup.mockReset();
|
||||
});
|
||||
const esClient = elasticsearchServiceMock.createInternalClient();
|
||||
|
||||
describe('create', () => {
|
||||
it('work with a predefined id', async () => {
|
||||
const soClient = getMockedSoClient();
|
||||
|
||||
await downloadSourceService.create(
|
||||
soClient,
|
||||
esClient,
|
||||
{
|
||||
host: 'http://test.co',
|
||||
is_default: false,
|
||||
|
@ -175,6 +185,7 @@ describe('Download Service', () => {
|
|||
|
||||
await downloadSourceService.create(
|
||||
soClient,
|
||||
esClient,
|
||||
{
|
||||
is_default: true,
|
||||
name: 'Test',
|
||||
|
@ -191,7 +202,7 @@ describe('Download Service', () => {
|
|||
defaultDownloadSourceId: 'existing-default-download-source',
|
||||
});
|
||||
|
||||
await downloadSourceService.create(soClient, {
|
||||
await downloadSourceService.create(soClient, esClient, {
|
||||
is_default: true,
|
||||
name: 'New default host',
|
||||
host: 'http://test.co',
|
||||
|
@ -204,6 +215,41 @@ describe('Download Service', () => {
|
|||
{ is_default: false }
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if encryptedSavedObject is not configured', async () => {
|
||||
const soClient = getMockedSoClient();
|
||||
mockedAppContextService.getEncryptedSavedObjectsSetup.mockReturnValue({
|
||||
canEncrypt: false,
|
||||
} as any);
|
||||
await expect(
|
||||
downloadSourceService.create(
|
||||
soClient,
|
||||
esClient,
|
||||
{
|
||||
is_default: true,
|
||||
name: 'Test',
|
||||
host: 'http://test.co',
|
||||
},
|
||||
{ id: 'download-source-test' }
|
||||
)
|
||||
).rejects.toThrow(`Agent binary source needs encrypted saved object api key to be set`);
|
||||
});
|
||||
|
||||
it('should work if encryptedSavedObject is configured', async () => {
|
||||
const soClient = getMockedSoClient();
|
||||
|
||||
await downloadSourceService.create(
|
||||
soClient,
|
||||
esClient,
|
||||
{
|
||||
is_default: true,
|
||||
name: 'Test',
|
||||
host: 'http://test.co',
|
||||
},
|
||||
{ id: 'download-source-test' }
|
||||
);
|
||||
expect(soClient.create).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
|
@ -212,7 +258,7 @@ describe('Download Service', () => {
|
|||
defaultDownloadSourceId: 'existing-default-download-source',
|
||||
});
|
||||
|
||||
await downloadSourceService.update(soClient, 'download-source-test', {
|
||||
await downloadSourceService.update(soClient, esClient, 'download-source-test', {
|
||||
is_default: true,
|
||||
name: 'New default',
|
||||
host: 'http://test.co',
|
||||
|
@ -237,7 +283,7 @@ describe('Download Service', () => {
|
|||
defaultDownloadSourceId: 'existing-default-download-source',
|
||||
});
|
||||
|
||||
await downloadSourceService.update(soClient, 'existing-default-download-source', {
|
||||
await downloadSourceService.update(soClient, esClient, 'existing-default-download-source', {
|
||||
is_default: true,
|
||||
name: 'Test',
|
||||
host: 'http://test.co',
|
||||
|
|
|
@ -4,7 +4,12 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import type { SavedObjectsClientContract } from '@kbn/core/server';
|
||||
import { omit } from 'lodash';
|
||||
import type {
|
||||
ElasticsearchClient,
|
||||
KibanaRequest,
|
||||
SavedObjectsClientContract,
|
||||
} from '@kbn/core/server';
|
||||
import type { SavedObject } from '@kbn/core/server';
|
||||
|
||||
import { SavedObjectNotFound } from '@kbn/kibana-utils-plugin/common';
|
||||
|
@ -15,30 +20,60 @@ import {
|
|||
DEFAULT_DOWNLOAD_SOURCE_ID,
|
||||
} from '../constants';
|
||||
|
||||
import type { DownloadSource, DownloadSourceSOAttributes, DownloadSourceBase } from '../types';
|
||||
import { DownloadSourceError, FleetError } from '../errors';
|
||||
import type {
|
||||
DownloadSource,
|
||||
DownloadSourceSOAttributes,
|
||||
DownloadSourceBase,
|
||||
PolicySecretReference,
|
||||
} from '../types';
|
||||
import {
|
||||
DownloadSourceError,
|
||||
FleetEncryptedSavedObjectEncryptionKeyRequired,
|
||||
FleetError,
|
||||
} from '../errors';
|
||||
import { SO_SEARCH_LIMIT } from '../../common';
|
||||
|
||||
import { deleteDownloadSourceSecrets, deleteSecrets, isSecretStorageEnabled } from './secrets';
|
||||
|
||||
import { agentPolicyService } from './agent_policy';
|
||||
import { appContextService } from './app_context';
|
||||
import { escapeSearchQueryPhrase } from './saved_object';
|
||||
import { getFleetProxy } from './fleet_proxies';
|
||||
import {
|
||||
extractAndWriteDownloadSourcesSecrets,
|
||||
extractAndUpdateDownloadSourceSecrets,
|
||||
} from './secrets';
|
||||
|
||||
function savedObjectToDownloadSource(so: SavedObject<DownloadSourceSOAttributes>) {
|
||||
const { source_id: sourceId, ...attributes } = so.attributes;
|
||||
const { ssl, source_id: sourceId, ...attributes } = so.attributes;
|
||||
|
||||
return {
|
||||
id: sourceId ?? so.id,
|
||||
name: attributes.name,
|
||||
host: attributes.host,
|
||||
is_default: attributes.is_default,
|
||||
proxy_id: attributes.proxy_id,
|
||||
...attributes,
|
||||
...(ssl ? { ssl: JSON.parse(ssl as string) } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
const fakeRequest = {
|
||||
headers: {},
|
||||
getBasePath: () => '',
|
||||
path: '/',
|
||||
route: { settings: {} },
|
||||
url: {
|
||||
href: '/',
|
||||
},
|
||||
raw: {
|
||||
req: {
|
||||
url: '/',
|
||||
},
|
||||
},
|
||||
} as unknown as KibanaRequest;
|
||||
class DownloadSourceService {
|
||||
private get encryptedSoClient() {
|
||||
return appContextService.getInternalUserSOClient(fakeRequest);
|
||||
}
|
||||
|
||||
public async get(soClient: SavedObjectsClientContract, id: string): Promise<DownloadSource> {
|
||||
const soResponse = await soClient.get<DownloadSourceSOAttributes>(
|
||||
const soResponse = await this.encryptedSoClient.get<DownloadSourceSOAttributes>(
|
||||
DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
|
||||
id
|
||||
);
|
||||
|
@ -51,7 +86,7 @@ class DownloadSourceService {
|
|||
}
|
||||
|
||||
public async list(soClient: SavedObjectsClientContract) {
|
||||
const downloadSources = await soClient.find<DownloadSourceSOAttributes>({
|
||||
const downloadSources = await this.encryptedSoClient.find<DownloadSourceSOAttributes>({
|
||||
type: DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
|
||||
page: 1,
|
||||
perPage: SO_SEARCH_LIMIT,
|
||||
|
@ -69,13 +104,20 @@ class DownloadSourceService {
|
|||
|
||||
public async create(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
downloadSource: DownloadSourceBase,
|
||||
options?: { id?: string; overwrite?: boolean }
|
||||
): Promise<DownloadSource> {
|
||||
const logger = appContextService.getLogger();
|
||||
logger.debug(`Creating new download source`);
|
||||
|
||||
const data: DownloadSourceSOAttributes = downloadSource;
|
||||
const data: DownloadSourceSOAttributes = { ...omit(downloadSource, ['ssl', 'secrets']) };
|
||||
|
||||
if (!appContextService.getEncryptedSavedObjectsSetup()?.canEncrypt) {
|
||||
throw new FleetEncryptedSavedObjectEncryptionKeyRequired(
|
||||
`Agent binary source needs encrypted saved object api key to be set`
|
||||
);
|
||||
}
|
||||
|
||||
await this.requireUniqueName(soClient, {
|
||||
name: downloadSource.name,
|
||||
|
@ -91,14 +133,32 @@ class DownloadSourceService {
|
|||
const defaultDownloadSourceId = await this.getDefaultDownloadSourceId(soClient);
|
||||
|
||||
if (defaultDownloadSourceId) {
|
||||
await this.update(soClient, defaultDownloadSourceId, { is_default: false });
|
||||
await this.update(soClient, esClient, defaultDownloadSourceId, { is_default: false });
|
||||
}
|
||||
}
|
||||
if (options?.id) {
|
||||
data.source_id = options?.id;
|
||||
}
|
||||
if (downloadSource.ssl) {
|
||||
data.ssl = JSON.stringify(downloadSource.ssl);
|
||||
}
|
||||
// Store secret values if enabled; if not, store plain text values
|
||||
if (await isSecretStorageEnabled(esClient, soClient)) {
|
||||
const { downloadSource: downloadSourceWithSecrets } =
|
||||
await extractAndWriteDownloadSourcesSecrets({
|
||||
downloadSource,
|
||||
esClient,
|
||||
});
|
||||
|
||||
const newSo = await soClient.create<DownloadSourceSOAttributes>(
|
||||
if (downloadSourceWithSecrets.secrets)
|
||||
data.secrets = downloadSourceWithSecrets.secrets as DownloadSourceSOAttributes['secrets'];
|
||||
} else {
|
||||
if (!downloadSource.ssl?.key && downloadSource.secrets?.ssl?.key) {
|
||||
data.ssl = JSON.stringify({ ...downloadSource.ssl, ...downloadSource.secrets.ssl });
|
||||
}
|
||||
}
|
||||
|
||||
const newSo = await this.encryptedSoClient.create<DownloadSourceSOAttributes>(
|
||||
DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
|
||||
data,
|
||||
{
|
||||
|
@ -112,12 +172,19 @@ class DownloadSourceService {
|
|||
|
||||
public async update(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
id: string,
|
||||
newData: Partial<DownloadSource>
|
||||
) {
|
||||
let secretsToDelete: PolicySecretReference[] = [];
|
||||
|
||||
const logger = appContextService.getLogger();
|
||||
logger.debug(`Updating download source ${id} with ${newData}`);
|
||||
const updateData: Partial<DownloadSourceSOAttributes> = newData;
|
||||
|
||||
const originalItem = await this.get(soClient, id);
|
||||
const updateData: Partial<DownloadSourceSOAttributes> = {
|
||||
...omit(newData, ['ssl', 'secrets']),
|
||||
};
|
||||
|
||||
if (updateData.proxy_id) {
|
||||
await this.throwIfProxyNotFound(soClient, updateData.proxy_id);
|
||||
|
@ -129,15 +196,46 @@ class DownloadSourceService {
|
|||
id,
|
||||
});
|
||||
}
|
||||
if (newData.ssl) {
|
||||
updateData.ssl = JSON.stringify(newData.ssl);
|
||||
} else if (newData.ssl === null) {
|
||||
// Explicitly set to null to allow to delete the field
|
||||
updateData.ssl = null;
|
||||
}
|
||||
|
||||
if (updateData.is_default) {
|
||||
const defaultDownloadSourceId = await this.getDefaultDownloadSourceId(soClient);
|
||||
|
||||
if (defaultDownloadSourceId && defaultDownloadSourceId !== id) {
|
||||
await this.update(soClient, defaultDownloadSourceId, { is_default: false });
|
||||
await this.update(soClient, esClient, defaultDownloadSourceId, { is_default: false });
|
||||
}
|
||||
}
|
||||
const soResponse = await soClient.update<DownloadSourceSOAttributes>(
|
||||
// Store secret values if enabled; if not, store plain text values
|
||||
if (await isSecretStorageEnabled(esClient, soClient)) {
|
||||
const secretsRes = await extractAndUpdateDownloadSourceSecrets({
|
||||
oldDownloadSource: originalItem,
|
||||
downloadSourceUpdate: newData,
|
||||
esClient,
|
||||
});
|
||||
|
||||
updateData.secrets = secretsRes.downloadSourceUpdate
|
||||
.secrets as DownloadSourceSOAttributes['secrets'];
|
||||
secretsToDelete = secretsRes.secretsToDelete;
|
||||
} else {
|
||||
if (!newData.ssl?.key && newData.secrets?.ssl?.key) {
|
||||
updateData.ssl = JSON.stringify({ ...newData.ssl, ...newData.secrets.ssl });
|
||||
}
|
||||
}
|
||||
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
|
||||
const soResponse = await this.encryptedSoClient.update<DownloadSourceSOAttributes>(
|
||||
DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
|
||||
id,
|
||||
updateData
|
||||
|
@ -162,8 +260,13 @@ class DownloadSourceService {
|
|||
appContextService.getInternalUserESClient(),
|
||||
id
|
||||
);
|
||||
logger.debug(`Deleted download source ${id}`);
|
||||
return soClient.delete(DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE, id);
|
||||
await deleteDownloadSourceSecrets({
|
||||
esClient: appContextService.getInternalUserESClient(),
|
||||
downloadSource: targetDS,
|
||||
});
|
||||
|
||||
logger.debug(`Deleting download source ${id}`);
|
||||
return this.encryptedSoClient.delete(DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE, id);
|
||||
}
|
||||
|
||||
public async getDefaultDownloadSourceId(soClient: SavedObjectsClientContract) {
|
||||
|
@ -176,7 +279,7 @@ class DownloadSourceService {
|
|||
return savedObjectToDownloadSource(results.saved_objects[0]).id;
|
||||
}
|
||||
|
||||
public async ensureDefault(soClient: SavedObjectsClientContract) {
|
||||
public async ensureDefault(soClient: SavedObjectsClientContract, esClient: ElasticsearchClient) {
|
||||
const downloadSources = await this.list(soClient);
|
||||
|
||||
const defaultDS = downloadSources.items.find((o) => o.is_default);
|
||||
|
@ -188,7 +291,7 @@ class DownloadSourceService {
|
|||
host: DEFAULT_DOWNLOAD_SOURCE_URI,
|
||||
};
|
||||
|
||||
return await this.create(soClient, newDefaultDS, {
|
||||
return await this.create(soClient, esClient, newDefaultDS, {
|
||||
id: DEFAULT_DOWNLOAD_SOURCE_ID,
|
||||
overwrite: true,
|
||||
});
|
||||
|
@ -201,7 +304,7 @@ class DownloadSourceService {
|
|||
soClient: SavedObjectsClientContract,
|
||||
downloadSource: { name: string; id?: string }
|
||||
) {
|
||||
const results = await soClient.find<DownloadSourceSOAttributes>({
|
||||
const results = await this.encryptedSoClient.find<DownloadSourceSOAttributes>({
|
||||
type: DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
|
||||
searchFields: ['name'],
|
||||
search: escapeSearchQueryPhrase(downloadSource.name),
|
||||
|
@ -223,7 +326,7 @@ class DownloadSourceService {
|
|||
}
|
||||
|
||||
public async listAllForProxyId(soClient: SavedObjectsClientContract, proxyId: string) {
|
||||
const downloadSources = await soClient.find<DownloadSourceSOAttributes>({
|
||||
const downloadSources = await this.encryptedSoClient.find<DownloadSourceSOAttributes>({
|
||||
type: DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
|
||||
searchFields: ['proxy_id'],
|
||||
search: proxyId,
|
||||
|
@ -248,7 +351,7 @@ class DownloadSourceService {
|
|||
}
|
||||
|
||||
private async _getDefaultDownloadSourceSO(soClient: SavedObjectsClientContract) {
|
||||
return await soClient.find<DownloadSourceSOAttributes>({
|
||||
return await this.encryptedSoClient.find<DownloadSourceSOAttributes>({
|
||||
type: DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
|
||||
searchFields: ['is_default'],
|
||||
search: 'true',
|
||||
|
|
|
@ -224,7 +224,7 @@ async function updateRelatedSavedObject(
|
|||
);
|
||||
|
||||
await pMap(downloadSources, (downloadSource) =>
|
||||
downloadSourceService.update(soClient, downloadSource.id, {
|
||||
downloadSourceService.update(soClient, esClient, downloadSource.id, {
|
||||
...omit(downloadSource, 'id'),
|
||||
proxy_id: null,
|
||||
})
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -17,6 +17,8 @@ import type {
|
|||
NewFleetServerHost,
|
||||
NewRemoteElasticsearchOutput,
|
||||
Output,
|
||||
DownloadSource,
|
||||
DownloadSourceBase,
|
||||
} from '../../common/types';
|
||||
|
||||
import { packageHasNoPolicyTemplates } from '../../common/services/policy_template';
|
||||
|
@ -475,9 +477,7 @@ export async function isOutputSecretStorageEnabled(
|
|||
return true;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
'Output secrets storage is disabled as minimum fleet server version has not been met'
|
||||
);
|
||||
logger.info('Secrets storage is disabled as minimum fleet server version has not been met');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -952,9 +952,8 @@ export async function extractAndWriteFleetServerHostsSecrets(opts: {
|
|||
}): Promise<{ fleetServerHost: NewFleetServerHost; secretReferences: PolicySecretReference[] }> {
|
||||
const { fleetServerHost, esClient, secretHashes = {} } = opts;
|
||||
|
||||
const secretPaths = getFleetServerHostsSecretPaths(fleetServerHost).filter(
|
||||
(path) => typeof path.value === 'string'
|
||||
);
|
||||
const secretPaths = getFleetServerHostsSecretPaths(fleetServerHost);
|
||||
|
||||
const secretRes = await extractAndWriteSOSecrets<NewFleetServerHost>({
|
||||
soObject: fleetServerHost,
|
||||
secretPaths,
|
||||
|
@ -1000,10 +999,7 @@ export async function deleteFleetServerHostsSecrets(opts: {
|
|||
}): Promise<void> {
|
||||
const { fleetServerHost, esClient } = opts;
|
||||
|
||||
const secretPaths = getFleetServerHostsSecretPaths(fleetServerHost).filter(
|
||||
(path) => typeof path.value === 'string'
|
||||
);
|
||||
|
||||
const secretPaths = getFleetServerHostsSecretPaths(fleetServerHost);
|
||||
await deleteSOSecrets(esClient, secretPaths);
|
||||
}
|
||||
|
||||
|
@ -1025,3 +1021,91 @@ export function getFleetServerHostsSecretReferences(
|
|||
|
||||
return secretPaths;
|
||||
}
|
||||
|
||||
// Download sources functions
|
||||
function getDownloadSourcesSecretPaths(
|
||||
downloadSource: DownloadSource | Partial<DownloadSource>
|
||||
): SOSecretPath[] {
|
||||
const secretPaths: SOSecretPath[] = [];
|
||||
|
||||
if (downloadSource?.secrets?.ssl?.key) {
|
||||
secretPaths.push({
|
||||
path: 'secrets.ssl.key',
|
||||
value: downloadSource.secrets.ssl.key,
|
||||
});
|
||||
}
|
||||
return secretPaths;
|
||||
}
|
||||
|
||||
export async function extractAndWriteDownloadSourcesSecrets(opts: {
|
||||
downloadSource: DownloadSourceBase;
|
||||
esClient: ElasticsearchClient;
|
||||
secretHashes?: Record<string, any>;
|
||||
}): Promise<{ downloadSource: DownloadSourceBase; secretReferences: PolicySecretReference[] }> {
|
||||
const { downloadSource, esClient, secretHashes = {} } = opts;
|
||||
|
||||
const secretPaths = getFleetServerHostsSecretPaths(downloadSource).filter(
|
||||
(path) => typeof path.value === 'string'
|
||||
);
|
||||
const secretRes = await extractAndWriteSOSecrets<DownloadSourceBase>({
|
||||
soObject: downloadSource,
|
||||
secretPaths,
|
||||
esClient,
|
||||
secretHashes,
|
||||
});
|
||||
return {
|
||||
downloadSource: secretRes.soObjectWithSecrets,
|
||||
secretReferences: secretRes.secretReferences,
|
||||
};
|
||||
}
|
||||
|
||||
export async function extractAndUpdateDownloadSourceSecrets(opts: {
|
||||
oldDownloadSource: DownloadSourceBase;
|
||||
downloadSourceUpdate: Partial<DownloadSourceBase>;
|
||||
esClient: ElasticsearchClient;
|
||||
secretHashes?: Record<string, any>;
|
||||
}): Promise<{
|
||||
downloadSourceUpdate: Partial<DownloadSourceBase>;
|
||||
secretReferences: PolicySecretReference[];
|
||||
secretsToDelete: PolicySecretReference[];
|
||||
}> {
|
||||
const { oldDownloadSource, downloadSourceUpdate, esClient, secretHashes } = opts;
|
||||
const oldSecretPaths = getDownloadSourcesSecretPaths(oldDownloadSource);
|
||||
const updatedSecretPaths = getDownloadSourcesSecretPaths(downloadSourceUpdate);
|
||||
const secretsRes = await extractAndUpdateSOSecrets<DownloadSourceBase>({
|
||||
updatedSoObject: downloadSourceUpdate,
|
||||
oldSecretPaths,
|
||||
updatedSecretPaths,
|
||||
esClient,
|
||||
secretHashes,
|
||||
});
|
||||
return {
|
||||
downloadSourceUpdate: secretsRes.updatedSoObject,
|
||||
secretReferences: secretsRes.secretReferences,
|
||||
secretsToDelete: secretsRes.secretsToDelete,
|
||||
};
|
||||
}
|
||||
|
||||
export async function deleteDownloadSourceSecrets(opts: {
|
||||
downloadSource: DownloadSourceBase;
|
||||
esClient: ElasticsearchClient;
|
||||
}): Promise<void> {
|
||||
const { downloadSource, esClient } = opts;
|
||||
|
||||
const secretPaths = getDownloadSourcesSecretPaths(downloadSource);
|
||||
|
||||
await deleteSOSecrets(esClient, secretPaths);
|
||||
}
|
||||
|
||||
export function getDownloadSourceSecretReferences(
|
||||
downloadSource: DownloadSource
|
||||
): PolicySecretReference[] {
|
||||
const secretPaths: PolicySecretReference[] = [];
|
||||
|
||||
if (typeof downloadSource.secrets?.ssl?.key === 'object') {
|
||||
secretPaths.push({
|
||||
id: downloadSource.secrets.ssl.key.id,
|
||||
});
|
||||
}
|
||||
return secretPaths;
|
||||
}
|
||||
|
|
|
@ -188,7 +188,7 @@ async function createSetupSideEffects(
|
|||
logger.debug('Setting Fleet server config');
|
||||
await migrateSettingsToFleetServerHost(soClient, esClient);
|
||||
logger.debug('Setting up Fleet download source');
|
||||
const defaultDownloadSource = await downloadSourceService.ensureDefault(soClient);
|
||||
const defaultDownloadSource = await downloadSourceService.ensureDefault(soClient, esClient);
|
||||
// Need to be done before outputs and fleet server hosts as these object can reference a proxy
|
||||
logger.debug('Setting up Proxy');
|
||||
await ensurePreconfiguredFleetProxies(
|
||||
|
|
|
@ -245,6 +245,24 @@ function validateGlobalDataTagInput(tags: GlobalDataTag[]): string | undefined {
|
|||
}
|
||||
}
|
||||
|
||||
const BaseSSLSchema = schema.object({
|
||||
verification_mode: schema.maybe(schema.string()),
|
||||
certificate_authorities: schema.maybe(schema.arrayOf(schema.string())),
|
||||
certificate: schema.maybe(schema.string()),
|
||||
key: schema.maybe(schema.string()),
|
||||
renegotiation: schema.maybe(schema.string()),
|
||||
});
|
||||
|
||||
const BaseSecretsSchema = schema.object({
|
||||
ssl: schema.maybe(
|
||||
schema.object({
|
||||
key: schema.object({
|
||||
id: schema.maybe(schema.string()),
|
||||
}),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
export const NewAgentPolicySchema = schema.object({
|
||||
...AgentPolicyBaseSchema,
|
||||
force: schema.maybe(schema.boolean()),
|
||||
|
@ -332,26 +350,8 @@ export const FullAgentPolicyResponseSchema = schema.object({
|
|||
hosts: schema.arrayOf(schema.string()),
|
||||
proxy_url: schema.maybe(schema.string()),
|
||||
proxy_headers: schema.maybe(schema.any()),
|
||||
ssl: schema.maybe(
|
||||
schema.object({
|
||||
verification_mode: schema.maybe(schema.string()),
|
||||
certificate_authorities: schema.maybe(schema.arrayOf(schema.string())),
|
||||
certificate: schema.maybe(schema.string()),
|
||||
key: schema.maybe(schema.string()),
|
||||
renegotiation: schema.maybe(schema.string()),
|
||||
})
|
||||
),
|
||||
secrets: schema.maybe(
|
||||
schema.object({
|
||||
ssl: schema.maybe(
|
||||
schema.object({
|
||||
key: schema.object({
|
||||
id: schema.maybe(schema.string()),
|
||||
}),
|
||||
})
|
||||
),
|
||||
})
|
||||
),
|
||||
ssl: schema.maybe(BaseSSLSchema),
|
||||
secrets: schema.maybe(BaseSecretsSchema),
|
||||
}),
|
||||
schema.object({
|
||||
kibana: schema.object({
|
||||
|
@ -435,6 +435,8 @@ export const FullAgentPolicyResponseSchema = schema.object({
|
|||
}),
|
||||
download: schema.object({
|
||||
sourceURI: schema.string(),
|
||||
ssl: schema.maybe(BaseSSLSchema),
|
||||
secrets: schema.maybe(BaseSecretsSchema),
|
||||
}),
|
||||
features: schema.recordOf(
|
||||
schema.string(),
|
||||
|
|
|
@ -7,6 +7,13 @@
|
|||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
const secretRefSchema = schema.oneOf([
|
||||
schema.object({
|
||||
id: schema.string(),
|
||||
}),
|
||||
schema.string(),
|
||||
]);
|
||||
|
||||
const DownloadSourceBaseSchema = {
|
||||
id: schema.maybe(schema.string()),
|
||||
name: schema.string(),
|
||||
|
@ -23,6 +30,18 @@ const DownloadSourceBaseSchema = {
|
|||
}),
|
||||
])
|
||||
),
|
||||
ssl: schema.maybe(
|
||||
schema.object({
|
||||
certificate_authorities: schema.maybe(schema.arrayOf(schema.string())),
|
||||
certificate: schema.maybe(schema.string()),
|
||||
key: schema.maybe(schema.string()),
|
||||
})
|
||||
),
|
||||
secrets: schema.maybe(
|
||||
schema.object({
|
||||
ssl: schema.maybe(schema.object({ key: schema.maybe(secretRefSchema) })),
|
||||
})
|
||||
),
|
||||
};
|
||||
|
||||
export const DownloadSourceSchema = schema.object({ ...DownloadSourceBaseSchema });
|
||||
|
|
|
@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema';
|
|||
|
||||
import { isDiffPathProtocol } from '../../../common/services';
|
||||
|
||||
import { OutputSchema } from '../models';
|
||||
import { DownloadSourceResponseSchema, OutputSchema } from '../models';
|
||||
|
||||
import { FleetProxySchema } from './fleet_proxies';
|
||||
import { FleetServerHostSchema } from './fleet_server_policy_config';
|
||||
|
@ -119,24 +119,6 @@ export const GetEnrollmentSettingsResponseSchema = schema.object({
|
|||
es_output: schema.maybe(OutputSchema),
|
||||
es_output_proxy: schema.maybe(FleetProxySchema),
|
||||
}),
|
||||
download_source: schema.maybe(
|
||||
schema.object({
|
||||
id: schema.string(),
|
||||
name: schema.string(),
|
||||
host: schema.string(),
|
||||
is_default: schema.boolean(),
|
||||
proxy_id: schema.maybe(
|
||||
schema.oneOf([
|
||||
schema.literal(null),
|
||||
schema.string({
|
||||
meta: {
|
||||
description:
|
||||
'The ID of the proxy to use for this download source. See the proxies API for more information.',
|
||||
},
|
||||
}),
|
||||
])
|
||||
),
|
||||
})
|
||||
),
|
||||
download_source: DownloadSourceResponseSchema,
|
||||
download_source_proxy: schema.maybe(FleetProxySchema),
|
||||
});
|
||||
|
|
|
@ -275,5 +275,11 @@ export interface DownloadSourceSOAttributes {
|
|||
is_default: boolean;
|
||||
source_id?: string;
|
||||
proxy_id?: string | null;
|
||||
ssl?: string | null; // encrypted ssl field
|
||||
secrets?: {
|
||||
ssl?: {
|
||||
key?: { id: string };
|
||||
};
|
||||
};
|
||||
}
|
||||
export type SimpleSOAssetAttributes = SimpleSOAssetType['attributes'];
|
||||
|
|
|
@ -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';
|
||||
import { testUsers } from '../test_users';
|
||||
|
@ -18,15 +19,106 @@ export default function (providerContext: FtrProviderContext) {
|
|||
const esArchiver = getService('esArchiver');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const fleetAndAgents = getService('fleetAndAgents');
|
||||
const es = getService('es');
|
||||
|
||||
const clearAgents = async () => {
|
||||
try {
|
||||
await es.deleteByQuery({
|
||||
index: '.fleet-agents',
|
||||
refresh: true,
|
||||
query: {
|
||||
match_all: {},
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
// index doesn't exist
|
||||
}
|
||||
};
|
||||
|
||||
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 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 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
|
||||
}
|
||||
};
|
||||
|
||||
describe('fleet_download_sources_crud', function () {
|
||||
let defaultDownloadSourceId: string;
|
||||
let fleetServerPolicyId: string;
|
||||
skipIfNoDockerRegistry(providerContext);
|
||||
before(async () => {
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/fleet/empty_fleet_server');
|
||||
await fleetAndAgents.setup();
|
||||
|
||||
const { body: apiResponse } = await supertest
|
||||
.post(`/api/fleet/agent_policies`)
|
||||
.set('kbn-xsrf', 'kibana')
|
||||
.send({
|
||||
name: 'Default Fleet Server policy',
|
||||
namespace: 'default',
|
||||
has_fleet_server: true,
|
||||
is_default: true,
|
||||
})
|
||||
.expect(200);
|
||||
const fleetServerPolicy = apiResponse.item;
|
||||
fleetServerPolicyId = fleetServerPolicy.id;
|
||||
await createFleetServerPolicy(fleetServerPolicyId);
|
||||
|
||||
const { body: response } = await supertest
|
||||
.get(`/api/fleet/agent_download_sources`)
|
||||
.expect(200);
|
||||
|
@ -36,6 +128,7 @@ export default function (providerContext: FtrProviderContext) {
|
|||
throw new Error('default download source not set');
|
||||
}
|
||||
defaultDownloadSourceId = defaultDownloadSource.id;
|
||||
await deleteAllSecrets();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
|
@ -109,66 +202,6 @@ export default function (providerContext: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
|
||||
describe('PUT /agent_download_sources/{sourceId}', () => {
|
||||
it('should allow to update an existing download source', async function () {
|
||||
await supertest
|
||||
.put(`/api/fleet/agent_download_sources/${defaultDownloadSourceId}`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'new host1',
|
||||
host: 'https://test.co:403',
|
||||
is_default: false,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const {
|
||||
body: { item: downloadSource },
|
||||
} = await supertest
|
||||
.get(`/api/fleet/agent_download_sources/${defaultDownloadSourceId}`)
|
||||
.expect(200);
|
||||
|
||||
expect(downloadSource.host).to.eql('https://test.co:403');
|
||||
});
|
||||
|
||||
it('should allow to update is_default for existing download source', async function () {
|
||||
await supertest
|
||||
.put(`/api/fleet/agent_download_sources/${defaultDownloadSourceId}`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'new default host',
|
||||
host: 'https://test.co',
|
||||
is_default: true,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
await supertest.get(`/api/fleet/agent_download_sources`).expect(200);
|
||||
});
|
||||
|
||||
it('should return a 404 when updating a non existing download source', async function () {
|
||||
await supertest
|
||||
.put(`/api/fleet/agent_download_sources/idonotexists`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'new host1',
|
||||
host: 'https://test.co',
|
||||
is_default: true,
|
||||
})
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it('should return a 400 when passing a host that is not a valid uri', async function () {
|
||||
await supertest
|
||||
.put(`/api/fleet/agent_download_sources/${defaultDownloadSourceId}`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'new host1',
|
||||
host: 'not a valid uri',
|
||||
is_default: true,
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /agent_download_sources', () => {
|
||||
it('should allow to create a new download source host', async function () {
|
||||
const { body: postResponse } = await supertest
|
||||
|
@ -232,16 +265,310 @@ export default function (providerContext: FtrProviderContext) {
|
|||
})
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should allow to create a new download source host with ssl fields', async function () {
|
||||
const { body: postResponse } = await supertest
|
||||
.post(`/api/fleet/agent_download_sources`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'My download source with ssl',
|
||||
host: 'http://test.fr:443',
|
||||
is_default: false,
|
||||
ssl: {
|
||||
certificate: 'cert',
|
||||
certificate_authorities: ['ca'],
|
||||
key: 'KEY1',
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const { id: _, ...itemWithoutId } = postResponse.item;
|
||||
expect(itemWithoutId).to.eql({
|
||||
name: 'My download source with ssl',
|
||||
host: 'http://test.fr:443',
|
||||
is_default: false,
|
||||
ssl: {
|
||||
certificate: 'cert',
|
||||
certificate_authorities: ['ca'],
|
||||
key: 'KEY1',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should not allow ssl.key and secrets.ssl.key to be set at the same time', async function () {
|
||||
const res = await supertest
|
||||
.post(`/api/fleet/agent_download_sources`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: `My download source ${Date.now()}`,
|
||||
host: 'http://test.fr:443',
|
||||
is_default: false,
|
||||
ssl: {
|
||||
certificate_authorities: ['cert authorities'],
|
||||
certificate: 'path/to/cert',
|
||||
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 store secrets if fleet server does not meet minimum version', async function () {
|
||||
await clearAgents();
|
||||
await createFleetServerAgent(fleetServerPolicyId, 'server_1', '7.0.0');
|
||||
|
||||
const { body: res } = await supertest
|
||||
.post(`/api/fleet/agent_download_sources`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: `My download source ${Date.now()}`,
|
||||
host: 'http://test.fr:443',
|
||||
is_default: false,
|
||||
secrets: {
|
||||
ssl: {
|
||||
key: 'KEY1',
|
||||
},
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
expect(Object.keys(res.item)).not.to.contain('secrets');
|
||||
expect(Object.keys(res.item)).to.contain('ssl');
|
||||
expect(Object.keys(res.item.ssl)).to.contain('key');
|
||||
expect(res.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/agent_download_sources`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: `My download source ${Date.now()}`,
|
||||
host: 'http://test.fr:443',
|
||||
is_default: false,
|
||||
ssl: {
|
||||
certificate_authorities: ['cert authorities'],
|
||||
certificate: 'path/to/cert',
|
||||
},
|
||||
secrets: { ssl: { key: 'KEY1' } },
|
||||
})
|
||||
.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');
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /agent_download_sources/{sourceId}', () => {
|
||||
it('should allow to update an existing download source', async function () {
|
||||
await supertest
|
||||
.put(`/api/fleet/agent_download_sources/${defaultDownloadSourceId}`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'new host1',
|
||||
host: 'https://test.co:403',
|
||||
is_default: false,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const {
|
||||
body: { item: downloadSource },
|
||||
} = await supertest
|
||||
.get(`/api/fleet/agent_download_sources/${defaultDownloadSourceId}`)
|
||||
.expect(200);
|
||||
|
||||
expect(downloadSource.host).to.eql('https://test.co:403');
|
||||
});
|
||||
|
||||
it('should allow to update is_default for existing download source', async function () {
|
||||
await supertest
|
||||
.put(`/api/fleet/agent_download_sources/${defaultDownloadSourceId}`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'new default host',
|
||||
host: 'https://test.co',
|
||||
is_default: true,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
await supertest.get(`/api/fleet/agent_download_sources`).expect(200);
|
||||
});
|
||||
|
||||
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
|
||||
.put(`/api/fleet/agent_download_sources/${defaultDownloadSourceId}`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'new default host',
|
||||
host: 'https://test.co',
|
||||
is_default: true,
|
||||
ssl: {
|
||||
certificate_authorities: ['cert authorities'],
|
||||
certificate: 'path/to/cert',
|
||||
},
|
||||
secrets: { ssl: { key: 'KEY1' } },
|
||||
})
|
||||
.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');
|
||||
});
|
||||
|
||||
it('should allow secrets to be updated + delete unused secret', async function () {
|
||||
await clearAgents();
|
||||
await createFleetServerAgent(fleetServerPolicyId, 'server_1', '8.12.0');
|
||||
|
||||
const res = await supertest
|
||||
.post(`/api/fleet/agent_download_sources`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'My download source with secrets',
|
||||
host: 'http://test.fr:443',
|
||||
is_default: false,
|
||||
ssl: {
|
||||
certificate_authorities: ['cert authorities'],
|
||||
certificate: 'path/to/cert',
|
||||
},
|
||||
secrets: { ssl: { key: 'KEY1' } },
|
||||
})
|
||||
.expect(200);
|
||||
const dsId = 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/agent_download_sources/${dsId}`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'My download source with secrets',
|
||||
host: 'http://test.fr:443',
|
||||
is_default: false,
|
||||
ssl: {
|
||||
certificate_authorities: ['cert authorities'],
|
||||
certificate: 'path/to/cert',
|
||||
},
|
||||
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
|
||||
}
|
||||
});
|
||||
|
||||
it('should allow to resave ssl.key as secret if already existing', async function () {
|
||||
await clearAgents();
|
||||
await createFleetServerAgent(fleetServerPolicyId, 'server_1', '8.12.0');
|
||||
|
||||
const res = await supertest
|
||||
.post(`/api/fleet/agent_download_sources`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: `My download source ${Date.now()}`,
|
||||
host: 'http://test.fr:443',
|
||||
is_default: false,
|
||||
ssl: {
|
||||
certificate_authorities: ['cert authorities'],
|
||||
certificate: 'path/to/cert',
|
||||
key: 'KEY1',
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
const dsId = res.body.item.id;
|
||||
|
||||
const updatedRes = await supertest
|
||||
.put(`/api/fleet/agent_download_sources/${dsId}`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: `My download source ${Date.now()}`,
|
||||
host: 'http://test.fr:443',
|
||||
is_default: false,
|
||||
ssl: {
|
||||
certificate_authorities: ['cert authorities'],
|
||||
certificate: 'path/to/cert',
|
||||
},
|
||||
secrets: { ssl: { key: 'NEW_KEY' } },
|
||||
})
|
||||
.expect(200);
|
||||
const updatedSecretId = updatedRes.body.item.secrets.ssl.key.id;
|
||||
|
||||
const updatedSecret = await getSecretById(updatedSecretId);
|
||||
|
||||
// @ts-ignore _source unknown type
|
||||
expect(updatedSecret._source.value).to.equal('NEW_KEY');
|
||||
});
|
||||
|
||||
it('should return a 404 when updating a non existing download source', async function () {
|
||||
await supertest
|
||||
.put(`/api/fleet/agent_download_sources/idonotexists`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'new host1',
|
||||
host: 'https://test.co',
|
||||
is_default: true,
|
||||
})
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it('should return a 400 when passing a host that is not a valid uri', async function () {
|
||||
await supertest
|
||||
.put(`/api/fleet/agent_download_sources/${defaultDownloadSourceId}`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'new host1',
|
||||
host: 'not a valid uri',
|
||||
is_default: true,
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('proxy_id behaviour', () => {
|
||||
const PROXY_ID = 'download-source-proxy-id';
|
||||
before(async () => {
|
||||
await supertest.post(`/api/fleet/proxies`).set('kbn-xsrf', 'xxxx').send({
|
||||
id: PROXY_ID,
|
||||
name: 'Download source proxy test',
|
||||
url: 'https://some.source.proxy:3232',
|
||||
});
|
||||
await supertest
|
||||
.post(`/api/fleet/fleet_server_hosts`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: `Default ${Date.now()}`,
|
||||
host_urls: ['https://test.fr:8080'],
|
||||
is_default: true,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
await supertest
|
||||
.post(`/api/fleet/proxies`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
id: PROXY_ID,
|
||||
name: 'Download source proxy test',
|
||||
url: 'https://some.source.proxy:3232',
|
||||
})
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
it('should allow creating a new download source host with a proxy_id ', async function () {
|
||||
|
@ -288,14 +615,39 @@ export default function (providerContext: FtrProviderContext) {
|
|||
description: '',
|
||||
is_default: false,
|
||||
download_source_id: downloadSourceId,
|
||||
});
|
||||
|
||||
})
|
||||
.expect(200);
|
||||
const { id: agentPolicyId } = postAgentPolicyResponse.item;
|
||||
await supertest
|
||||
.post(`/api/fleet/package_policies`)
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.send({
|
||||
force: true,
|
||||
package: {
|
||||
name: 'fleet_server',
|
||||
version: '1.3.1',
|
||||
},
|
||||
name: `Fleet Server 1`,
|
||||
namespace: 'default',
|
||||
policy_ids: [agentPolicyId],
|
||||
vars: {},
|
||||
inputs: {
|
||||
'fleet_server-fleet-server': {
|
||||
enabled: true,
|
||||
vars: {
|
||||
custom: '',
|
||||
},
|
||||
streams: {},
|
||||
},
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const { body: getAgentPolicyResponse } = await supertest
|
||||
.get(`/api/fleet/agent_policies/${agentPolicyId}/full`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send();
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
expect(getAgentPolicyResponse.item.agent.download.proxy_url).to.eql(
|
||||
'https://some.source.proxy:3232'
|
||||
|
@ -307,7 +659,7 @@ export default function (providerContext: FtrProviderContext) {
|
|||
.post(`/api/fleet/agent_download_sources`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'My download source',
|
||||
name: 'My download source with invalid proxy',
|
||||
host: 'http://test.fr:443',
|
||||
proxy_id: 'this-proxy-id-does-not-exist',
|
||||
is_default: false,
|
||||
|
@ -436,6 +788,37 @@ export default function (providerContext: FtrProviderContext) {
|
|||
defaultDSIdToDelete = defaultDSPostResponse.item.id;
|
||||
});
|
||||
|
||||
it('should delete secrets when deleting a download source object', async function () {
|
||||
await clearAgents();
|
||||
await createFleetServerAgent(fleetServerPolicyId, 'server_1', '8.12.0');
|
||||
const res = await supertest
|
||||
.post(`/api/fleet/agent_download_sources`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: `My download source ${Date.now()}`,
|
||||
host: 'http://test.fr:443',
|
||||
is_default: false,
|
||||
ssl: {
|
||||
certificate_authorities: ['cert authorities'],
|
||||
certificate: 'path/to/cert',
|
||||
},
|
||||
secrets: { ssl: { key: 'KEY1' } },
|
||||
})
|
||||
.expect(200);
|
||||
const dsId = res.body.item.id;
|
||||
const secretId = res.body.item.secrets.ssl.key.id;
|
||||
await supertest
|
||||
.delete(`/api/fleet/agent_download_sources/${dsId}`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.expect(200);
|
||||
try {
|
||||
await getSecretById(secretId);
|
||||
expect().fail('Secret should have been deleted');
|
||||
} catch (e) {
|
||||
// not found
|
||||
}
|
||||
});
|
||||
|
||||
it('should return a 400 when trying to delete a default download source host ', async function () {
|
||||
await supertest
|
||||
.delete(`/api/fleet/agent_download_sources/${defaultDSIdToDelete}`)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue