Skip to content

Commit

Permalink
[Fleet] Fix wrong fleet server host URL in kubernetes manifest yaml w…
Browse files Browse the repository at this point in the history
…hen policy specifies non-default fleet server (#165127)

## Summary

If an agent policy used a fleet server host which is not the default
fleet server host, then when adding an agent the kubernetes manifest
would contain the incorrect URL.

This is because the settings request was returning all fleet servers not
the one for the policy, and we were picking the first one. I have opted
instead to pass the fleet server host down to the component from the
already filtered list we have higher up the component stack, we do the
same for the enrollment token.

**Test steps**

- add a custom fleet server URL (in Fleet settings)
- create an agent policy, use your custom URL (under agent policy
settings)
- go to add an agent to the policy, select kubernetes on the platform
selector
- Check the manifest yaml, under ` - name: FLEET_URL` should be the
correct custom URL
  • Loading branch information
hop-dev authored Aug 30, 2023
1 parent 5739f1f commit 5266327
Show file tree
Hide file tree
Showing 10 changed files with 110 additions and 21 deletions.
67 changes: 66 additions & 1 deletion x-pack/plugins/fleet/cypress/e2e/agent_policy.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
* 2.0.
*/
import { TOAST_CLOSE_BTN } from '../screens/navigation';

import { setupFleetServer } from '../tasks/fleet_server';
import { AGENT_FLYOUT, AGENT_POLICY_DETAILS_PAGE } from '../screens/fleet';
describe('Edit agent policy', () => {
beforeEach(() => {
cy.intercept('/api/fleet/agent_policies/policy-1', {
Expand Down Expand Up @@ -59,4 +60,68 @@ describe('Edit agent policy', () => {
expect(interception.request.body.description).to.equal('desc');
});
});

it('should show correct fleet server host for custom URL', () => {
setupFleetServer();

cy.intercept('/api/fleet/agent_policies/policy-1', {
item: {
id: 'policy-1',
name: 'Agent policy 1',
description: 'desc',
namespace: 'default',
monitoring_enabled: ['logs', 'metrics'],
status: 'active',
fleet_server_host_id: 'fleet-server-1',
package_policies: [],
},
});

const apiKey = {
id: 'key-1',
active: true,
api_key_id: 'PefGQYoB0MXWbqVD6jhr',
api_key: 'this-is-the-api-key',
name: 'key-1',
policy_id: 'policy-1',
created_at: '2023-08-29T14:51:10.473Z',
};

cy.intercept('/api/fleet/enrollment_api_keys?**', {
items: [apiKey],
total: 1,
page: 1,
perPage: 10000,
});
cy.intercept('/api/fleet/enrollment_api_keys/key-1', {
item: apiKey,
});
cy.intercept('/api/fleet/fleet_server_hosts', {
items: [
{
id: 'fleet-default-fleet-server-host',
name: 'Default',
is_default: true,
host_urls: ['https://192.168.1.23:8220'],
is_preconfigured: true,
},
{
id: 'fleet-server-1',
name: 'custom host',
host_urls: ['https://xxx.yyy.zzz:443'],
is_default: false,
is_preconfigured: false,
},
],
page: 1,
perPage: 10000,
total: 2,
});
cy.visit('/app/fleet/policies/policy-1');

cy.getBySel(AGENT_POLICY_DETAILS_PAGE.ADD_AGENT_LINK).click();
cy.getBySel(AGENT_FLYOUT.KUBERNETES_PLATFORM_TYPE).click();
cy.contains('https://xxx.yyy.zzz:443');
cy.contains('this-is-the-api-key');
});
});
5 changes: 5 additions & 0 deletions x-pack/plugins/fleet/cypress/screens/fleet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export const AGENT_FLYOUT = {
MANAGED_TAB: 'managedTab',
CONFIRM_AGENT_ENROLLMENT_BUTTON: 'ConfirmAgentEnrollmentButton',
INCOMING_DATA_CONFIRMED_CALL_OUT: 'IncomingDataConfirmedCallOut',
KUBERNETES_PLATFORM_TYPE: 'platformTypeKubernetes',
};

export const AGENT_POLICIES_CREATE_AGENT_POLICY_FLYOUT = {
Expand Down Expand Up @@ -228,3 +229,7 @@ export const FLEET_SERVER_SETUP = {
export const API_KEYS = {
REVOKE_KEY_BUTTON: 'enrollmentTokenTable.revokeBtn',
};

export const AGENT_POLICY_DETAILS_PAGE = {
ADD_AGENT_LINK: 'addAgentLink',
};
13 changes: 9 additions & 4 deletions x-pack/plugins/fleet/cypress/tasks/fleet_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,27 @@ import { createAgentDoc } from './agents';
const FLEET_SERVER_POLICY_ID = 'fleet-server-policy';

// Create a Fleet server policy
export function setupFleetServer() {
let policyId: string;
export async function setupFleetServer() {
const policyId: string = FLEET_SERVER_POLICY_ID;
let kibanaVersion: string;

cy.request({
method: 'POST',
url: '/api/fleet/agent_policies',
headers: { 'kbn-xsrf': 'xx' },
failOnStatusCode: false,
body: {
id: FLEET_SERVER_POLICY_ID,
name: 'Fleet Server policy',
namespace: 'default',
has_fleet_server: true,
},
}).then((response: any) => {
policyId = response.body.item.id;
}).then((response) => {
// 409 is expected if the policy already exists
// this allows the test to be run repeatedly in dev
if (response.status > 299 && response.status !== 409) {
throw new Error(`Failed to create Fleet Server policy: ${response.body.message}`);
}
});

cy.getKibanaVersion().then((version) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export const InstallElasticAgentManagedPageStep: React.FC<InstallAgentPageProps>
selectedApiKeyId: enrollmentAPIKey.id,
isComplete: commandCopied || !!enrolledAgentIds.length,
fullCopyButton: true,
fleetServerHost: fleetServerHosts?.[0],
onCopy: () => setCommandCopied(true),
}),
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const HeaderRightContent: React.FunctionComponent<HeaderRightContentProps
}

const addAgentLink = (
<EuiLink onClick={addAgent}>
<EuiLink onClick={addAgent} data-test-subj="addAgentLink">
{isFleetServerPolicy ? (
<FormattedMessage
id="xpack.fleet.policyDetails.addFleetServerButton"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';

import { useGetSettings, useStartServices } from '../../hooks';
import { useStartServices } from '../../hooks';

import { agentPolicyRouteService } from '../../../common';

Expand All @@ -28,19 +28,19 @@ interface Props {
enrollmentAPIKey?: string;
onCopy?: () => void;
onDownload?: () => void;
fleetServerHost?: string;
}

export const KubernetesInstructions: React.FunctionComponent<Props> = ({
enrollmentAPIKey,
onCopy,
onDownload,
fleetServerHost,
}) => {
const core = useStartServices();
const settings = useGetSettings();
const { notifications } = core;

const [yaml, setYaml] = useState<string>('');
const [fleetServer, setFleetServer] = useState<string | ''>();
const [copyButtonClicked, setCopyButtonClicked] = useState(false);
const [downloadButtonClicked, setDownloadButtonClicked] = useState(false);

Expand All @@ -59,13 +59,10 @@ export const KubernetesInstructions: React.FunctionComponent<Props> = ({
useEffect(() => {
async function fetchK8sManifest() {
try {
const fleetServerHosts = settings.data?.item.fleet_server_hosts;
let host = '';
if (fleetServerHosts !== undefined && fleetServerHosts.length !== 0) {
setFleetServer(fleetServerHosts[0]);
host = fleetServerHosts[0];
}
const query = { fleetServer: host, enrolToken: enrollmentAPIKey };
const query = {
enrolToken: enrollmentAPIKey,
...(fleetServerHost && { fleetServer: fleetServerHost }),
};
const res = await sendGetK8sManifest(query);
if (res.error) {
throw res.error;
Expand All @@ -85,7 +82,7 @@ export const KubernetesInstructions: React.FunctionComponent<Props> = ({
}
}
fetchK8sManifest();
}, [notifications.toasts, enrollmentAPIKey, settings.data?.item.fleet_server_hosts]);
}, [notifications.toasts, enrollmentAPIKey, fleetServerHost]);

const downloadDescription = (
<FormattedMessage
Expand Down Expand Up @@ -114,8 +111,13 @@ export const KubernetesInstructions: React.FunctionComponent<Props> = ({
</EuiCopy>
);

const searchParams = new URLSearchParams({
...(fleetServerHost && { fleetServer: fleetServerHost }),
...(enrollmentAPIKey && { enrolToken: enrollmentAPIKey }),
});

const downloadLink = core.http.basePath.prepend(
`${agentPolicyRouteService.getK8sFullDownloadPath()}?fleetServer=${fleetServer}&enrolToken=${enrollmentAPIKey}`
`${agentPolicyRouteService.getK8sFullDownloadPath()}${searchParams.toString()}`
);

const k8sDownloadYaml = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ export const ManagedSteps: React.FunctionComponent<InstructionProps> = ({
selectedApiKeyId,
isK8s,
cloudSecurityIntegration,
fleetServerHost: fleetServerHosts?.[0],
enrollToken,
})
);
Expand Down Expand Up @@ -310,16 +311,17 @@ export const ManagedSteps: React.FunctionComponent<InstructionProps> = ({
setSelectedPolicyId,
refreshAgentPolicies,
selectionType,
isK8s,
cloudSecurityIntegration,
installManagedCommands,
apiKeyData,
enrolledAgentIds,
mode,
setMode,
enrollToken,
installManagedCommands,
isK8s,
fleetServerHosts,
onClickViewAgents,
link,
enrolledAgentIds,
agentDataConfirmed,
installedPackagePolicy,
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const InstallManagedAgentStep = ({
isK8s,
cloudSecurityIntegration,
enrollToken,
fleetServerHost,
isComplete,
fullCopyButton,
onCopy,
Expand All @@ -34,6 +35,7 @@ export const InstallManagedAgentStep = ({
isK8s?: K8sMode;
cloudSecurityIntegration?: CloudSecurityIntegration | undefined;
enrollToken?: string;
fleetServerHost?: string;
installCommand: CommandsByPlatform;
isComplete?: boolean;
fullCopyButton?: boolean;
Expand All @@ -55,6 +57,7 @@ export const InstallManagedAgentStep = ({
enrollToken={enrollToken}
onCopy={onCopy}
fullCopyButton={fullCopyButton}
fleetServerHost={fleetServerHost}
/>
) : (
<React.Fragment />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface Props {
isK8s: K8sMode | undefined;
cloudSecurityIntegration: CloudSecurityIntegration | undefined;
enrollToken?: string;
fleetServerHost?: string;
fullCopyButton?: boolean;
isManaged?: boolean;
onCopy?: () => void;
Expand All @@ -29,6 +30,7 @@ export const InstallSection: React.FunctionComponent<Props> = ({
isK8s,
cloudSecurityIntegration,
enrollToken,
fleetServerHost,
fullCopyButton = false,
isManaged = true,
onCopy,
Expand All @@ -50,6 +52,7 @@ export const InstallSection: React.FunctionComponent<Props> = ({
hasK8sIntegrationMultiPage={isK8s === 'IS_KUBERNETES_MULTIPAGE'}
isManaged={isManaged}
enrollToken={enrollToken}
fleetServerHost={fleetServerHost}
/>
</>
);
Expand Down
3 changes: 3 additions & 0 deletions x-pack/plugins/fleet/public/components/platform_selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ interface Props {
hasFleetServer?: boolean;
enrollToken?: string | undefined;
fullCopyButton?: boolean;
fleetServerHost?: string;
onCopy?: () => void;
}

Expand All @@ -64,6 +65,7 @@ export const PlatformSelector: React.FunctionComponent<Props> = ({
isManaged,
enrollToken,
hasFleetServer,
fleetServerHost,
fullCopyButton,
onCopy,
}) => {
Expand Down Expand Up @@ -205,6 +207,7 @@ export const PlatformSelector: React.FunctionComponent<Props> = ({
onCopy={onCopy}
onDownload={onCopy}
enrollmentAPIKey={enrollToken}
fleetServerHost={fleetServerHost}
/>
<EuiSpacer size="s" />
</>
Expand Down

0 comments on commit 5266327

Please sign in to comment.