Skip to content

Commit

Permalink
Merge branch 'main' into eui-ghost/security
Browse files Browse the repository at this point in the history
  • Loading branch information
cee-chen authored Oct 18, 2023
2 parents 7206829 + 8bd62dd commit 21a36d6
Show file tree
Hide file tree
Showing 28 changed files with 497 additions and 97 deletions.
12 changes: 12 additions & 0 deletions .buildkite/pipelines/on_merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,18 @@ steps:
- exit_status: '*'
limit: 1

- command: .buildkite/scripts/steps/functional/osquery_cypress.sh
label: 'Osquery Cypress Tests'
agents:
queue: n2-4-spot
depends_on: build
timeout_in_minutes: 50
parallelism: 6
retry:
automatic:
- exit_status: '*'
limit: 1

- command: '.buildkite/scripts/steps/functional/on_merge_unsupported_ftrs.sh'
label: Trigger unsupported ftr tests
timeout_in_minutes: 10
Expand Down
12 changes: 0 additions & 12 deletions .buildkite/pipelines/on_merge_unsupported_ftrs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,3 @@ steps:
limit: 3
- exit_status: '*'
limit: 1

- command: .buildkite/scripts/steps/functional/osquery_cypress.sh
label: 'Osquery Cypress Tests'
agents:
queue: n2-4-spot
depends_on: build
timeout_in_minutes: 50
parallelism: 6
retry:
automatic:
- exit_status: '*'
limit: 1
34 changes: 34 additions & 0 deletions .buildkite/pipelines/pull_request/base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,18 @@ steps:
- exit_status: '*'
limit: 1

- command: .buildkite/scripts/steps/functional/osquery_cypress.sh
label: 'Osquery Cypress Tests'
agents:
queue: n2-4-spot
depends_on: build
timeout_in_minutes: 50
parallelism: 6
retry:
automatic:
- exit_status: '*'
limit: 1

- command: .buildkite/scripts/steps/functional/security_solution_burn.sh
label: 'Security Solution Cypress tests, burning changed specs'
agents:
Expand All @@ -198,6 +210,28 @@ steps:
automatic: false
soft_fail: true

- command: .buildkite/scripts/steps/functional/osquery_cypress_burn.sh
label: 'Osquery Cypress Tests, burning changed specs'
agents:
queue: n2-4-spot
depends_on: build
timeout_in_minutes: 50
soft_fail: true
retry:
automatic: false

- command: .buildkite/scripts/steps/functional/security_serverless_osquery.sh
label: 'Serverless Osquery Cypress Tests'
agents:
queue: n2-4-spot
depends_on: build
timeout_in_minutes: 50
parallelism: 6
retry:
automatic:
- exit_status: '*'
limit: 1

# status_exception: Native role management is not enabled in this Elasticsearch instance
# - command: .buildkite/scripts/steps/functional/security_serverless_defend_workflows.sh
# label: 'Serverless Security Defend Workflows Cypress Tests'
Expand Down
34 changes: 0 additions & 34 deletions .buildkite/pipelines/pull_request/osquery_cypress.yml

This file was deleted.

8 changes: 0 additions & 8 deletions .buildkite/scripts/pipelines/pull_request/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,14 +151,6 @@ const uploadPipeline = (pipelineContent: string | object) => {
pipeline.push(getPipeline('.buildkite/pipelines/pull_request/webpack_bundle_analyzer.yml'));
}

if (
((await doAnyChangesMatch([/^x-pack\/plugins\/osquery/, /^x-pack\/test\/osquery_cypress/])) ||
GITHUB_PR_LABELS.includes('ci:all-cypress-suites')) &&
!GITHUB_PR_LABELS.includes('ci:skip-cypress-osquery')
) {
pipeline.push(getPipeline('.buildkite/pipelines/pull_request/osquery_cypress.yml'));
}

if (
(await doAnyChangesMatch([
/\.docnav\.json$/,
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/fleet/common/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export { isPackageLimited, doesAgentPolicyAlreadyIncludePackage } from './limite
export { isValidNamespace, INVALID_NAMESPACE_CHARACTERS } from './is_valid_namespace';
export { isDiffPathProtocol } from './is_diff_path_protocol';
export { LicenseService } from './license';
export { isAgentUpgradeable } from './is_agent_upgradeable';
export * from './is_agent_upgradeable';
export {
isAgentRequestDiagnosticsSupported,
MINIMUM_DIAGNOSTICS_AGENT_VERSION,
Expand Down
47 changes: 40 additions & 7 deletions x-pack/plugins/fleet/common/services/is_agent_upgradeable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,22 @@

import type { Agent } from '../types/models/agent';

import { isAgentUpgradeable } from './is_agent_upgradeable';
import { getRecentUpgradeInfoForAgent, isAgentUpgradeable } from './is_agent_upgradeable';

const getAgent = ({
version,
upgradeable = false,
unenrolling = false,
unenrolled = false,
updating = false,
upgraded = false,
minutesSinceUpgrade,
}: {
version: string;
upgradeable?: boolean;
unenrolling?: boolean;
unenrolled?: boolean;
updating?: boolean;
upgraded?: boolean;
minutesSinceUpgrade?: number;
}): Agent => {
const agent: Agent = {
id: 'de9006e1-54a7-4320-b24e-927e6fe518a8',
Expand Down Expand Up @@ -101,8 +101,8 @@ const getAgent = ({
if (updating) {
agent.upgrade_started_at = new Date(Date.now()).toISOString();
}
if (upgraded) {
agent.upgraded_at = new Date(Date.now()).toISOString();
if (minutesSinceUpgrade) {
agent.upgraded_at = new Date(Date.now() - minutesSinceUpgrade * 6e4).toISOString();
}
return agent;
};
Expand Down Expand Up @@ -176,9 +176,42 @@ describe('Fleet - isAgentUpgradeable', () => {
isAgentUpgradeable(getAgent({ version: '7.9.0', upgradeable: true, updating: true }), '8.0.0')
).toBe(false);
});
it('returns true if agent was recently upgraded', () => {
it('returns false if the agent reports upgradeable but was upgraded less than 10 minutes ago', () => {
expect(
isAgentUpgradeable(getAgent({ version: '7.9.0', upgradeable: true, upgraded: true }), '8.0.0')
isAgentUpgradeable(
getAgent({ version: '7.9.0', upgradeable: true, minutesSinceUpgrade: 9 }),
'8.0.0'
)
).toBe(false);
});
it('returns true if agent reports upgradeable and was upgraded more than 10 minutes ago', () => {
expect(
isAgentUpgradeable(
getAgent({ version: '7.9.0', upgradeable: true, minutesSinceUpgrade: 11 }),
'8.0.0'
)
).toBe(true);
});
});

describe('hasAgentBeenUpgradedRecently', () => {
it('returns true if the agent was upgraded less than 10 minutes ago', () => {
expect(
getRecentUpgradeInfoForAgent(getAgent({ version: '7.9.0', minutesSinceUpgrade: 9 }))
.hasBeenUpgradedRecently
).toBe(true);
});

it('returns false if the agent was upgraded more than 10 minutes ago', () => {
expect(
getRecentUpgradeInfoForAgent(getAgent({ version: '7.9.0', minutesSinceUpgrade: 11 }))
.hasBeenUpgradedRecently
).toBe(false);
});

it('returns false if the agent does not have an upgrade_at field', () => {
expect(
getRecentUpgradeInfoForAgent(getAgent({ version: '7.9.0' })).hasBeenUpgradedRecently
).toBe(false);
});
});
24 changes: 24 additions & 0 deletions x-pack/plugins/fleet/common/services/is_agent_upgradeable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import semverGt from 'semver/functions/gt';

import type { Agent } from '../types';

export const AGENT_UPGRADE_COOLDOWN_IN_MIN = 10;

export function isAgentUpgradeable(
agent: Agent,
latestAgentVersion: string,
Expand All @@ -32,6 +34,10 @@ export function isAgentUpgradeable(
if (agent.upgrade_started_at && !agent.upgraded_at) {
return false;
}
// check that the agent has not been upgraded more recently than the monitoring period
if (getRecentUpgradeInfoForAgent(agent).hasBeenUpgradedRecently) {
return false;
}
if (versionToUpgrade !== undefined) {
return isNotDowngrade(agentVersion, versionToUpgrade);
}
Expand All @@ -56,3 +62,21 @@ const isNotDowngrade = (agentVersion: string, versionToUpgrade: string) => {

return semverGt(versionToUpgradeNumber, agentVersionNumber);
};

export function getRecentUpgradeInfoForAgent(agent: Agent): {
hasBeenUpgradedRecently: boolean;
timeToWaitMs: number;
} {
if (!agent.upgraded_at) {
return {
hasBeenUpgradedRecently: false,
timeToWaitMs: 0,
};
}

const elaspedSinceUpgradeInMillis = Date.now() - Date.parse(agent.upgraded_at);
const timeToWaitMs = AGENT_UPGRADE_COOLDOWN_IN_MIN * 6e4 - elaspedSinceUpgradeInMillis;
const hasBeenUpgradedRecently = elaspedSinceUpgradeInMillis / 6e4 < AGENT_UPGRADE_COOLDOWN_IN_MIN;

return { hasBeenUpgradedRecently, timeToWaitMs };
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import type { EuiComboBoxOptionOption } from '@elastic/eui';
import semverGt from 'semver/functions/gt';
import semverLt from 'semver/functions/lt';

import { AGENT_UPGRADE_COOLDOWN_IN_MIN } from '../../../../../../../common/services';

import { getMinVersion } from '../../../../../../../common/services/get_min_max_version';
import {
AGENT_UPDATING_TIMEOUT_HOURS,
Expand Down Expand Up @@ -361,14 +363,32 @@ export const AgentUpgradeAgentModal: React.FunctionComponent<AgentUpgradeAgentMo
defaultMessage="No selected agents are eligible for an upgrade. Please select one or more eligible agents."
/>
) : isSingleAgent ? (
<FormattedMessage
id="xpack.fleet.upgradeAgents.upgradeSingleDescription"
defaultMessage="This action will upgrade the agent running on '{hostName}' to version {version}. This action can not be undone. Are you sure you wish to continue?"
values={{
hostName: ((agents[0] as Agent).local_metadata.host as any).hostname,
version: getVersion(selectedVersion),
}}
/>
<>
<p>
<FormattedMessage
id="xpack.fleet.upgradeAgents.upgradeSingleDescription"
defaultMessage="This action will upgrade the agent running on '{hostName}' to version {version}. This action can not be undone. Are you sure you wish to continue?"
values={{
hostName: ((agents[0] as Agent).local_metadata.host as any).hostname,
version: getVersion(selectedVersion),
}}
/>
</p>
{isUpdating && (
<p>
<em>
<FormattedMessage
id="xpack.fleet.upgradeAgents.upgradeSingleTimeout"
// TODO: Add link to docs regarding agent upgrade cooldowns
defaultMessage="Note that you may only restart an upgrade every {minutes} minutes to ensure that the upgrade will not be rolled back."
values={{
minutes: AGENT_UPGRADE_COOLDOWN_IN_MIN,
}}
/>
</em>
</p>
)}
</>
) : (
<FormattedMessage
id="xpack.fleet.upgradeAgents.upgradeMultipleDescription"
Expand Down
26 changes: 25 additions & 1 deletion x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,18 @@ import semverGt from 'semver/functions/gt';
import semverMajor from 'semver/functions/major';
import semverMinor from 'semver/functions/minor';

import moment from 'moment';

import type { PostAgentUpgradeResponse } from '../../../common/types';
import type { PostAgentUpgradeRequestSchema, PostBulkAgentUpgradeRequestSchema } from '../../types';
import * as AgentService from '../../services/agents';
import { appContextService } from '../../services';
import { defaultFleetErrorHandler } from '../../errors';
import { isAgentUpgradeable } from '../../../common/services';
import {
getRecentUpgradeInfoForAgent,
isAgentUpgradeable,
AGENT_UPGRADE_COOLDOWN_IN_MIN,
} from '../../../common/services';
import { getMaxVersion } from '../../../common/services/get_min_max_version';
import { getAgentById } from '../../services/agents';
import type { Agent } from '../../types';
Expand Down Expand Up @@ -67,6 +73,24 @@ export const postAgentUpgradeHandler: RequestHandler<
}
}

const { hasBeenUpgradedRecently, timeToWaitMs } = getRecentUpgradeInfoForAgent(agent);
const timeToWaitString = moment
.utc(moment.duration(timeToWaitMs).asMilliseconds())
.format('mm[m]ss[s]');

if (hasBeenUpgradedRecently) {
return response.customError({
statusCode: 429,
body: {
message: `agent ${request.params.agentId} was upgraded less than ${AGENT_UPGRADE_COOLDOWN_IN_MIN} minutes ago. Please wait ${timeToWaitString} before trying again to ensure the upgrade will not be rolled back.`,
},
headers: {
// retry-after expects seconds
'retry-after': Math.ceil(timeToWaitMs / 1000).toString(),
},
});
}

if (agent.unenrollment_started_at || agent.unenrolled_at) {
return response.customError({
statusCode: 400,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ describe('sendUpgradeAgentsActions (plural)', () => {
const docs = (calledWith as estypes.BulkRequest)?.body
?.filter((i: any) => i.doc)
.map((i: any) => i.doc);

expect(ids).toEqual(idsToAction);
for (const doc of docs!) {
expect(doc).toHaveProperty('upgrade_started_at');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type { SavedObjectsClientContract, ElasticsearchClient } from '@kbn/core/
import { v4 as uuidv4 } from 'uuid';
import moment from 'moment';

import { isAgentUpgradeable } from '../../../common/services';
import { getRecentUpgradeInfoForAgent, isAgentUpgradeable } from '../../../common/services';

import type { Agent } from '../../types';

Expand Down Expand Up @@ -76,9 +76,10 @@ export async function upgradeBatch(
const latestAgentVersion = await getLatestAvailableVersion();
const upgradeableResults = await Promise.allSettled(
agentsToCheckUpgradeable.map(async (agent) => {
// Filter out agents currently unenrolling, unenrolled, or not upgradeable b/c of version check
// Filter out agents currently unenrolling, unenrolled, recently upgraded or not upgradeable b/c of version check
const isNotAllowed =
!options.force && !isAgentUpgradeable(agent, latestAgentVersion, options.version);
getRecentUpgradeInfoForAgent(agent).hasBeenUpgradedRecently ||
(!options.force && !isAgentUpgradeable(agent, latestAgentVersion, options.version));
if (isNotAllowed) {
throw new FleetError(`Agent ${agent.id} is not upgradeable`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ const logger = {

const mockNow = new Date('2023-09-20T15:11:04.105Z');

const STARTED_AT_MOCK_DATE = new Date();

const mockOptions = {
executionId: '',
startedAt: mockNow,
Expand Down
Loading

0 comments on commit 21a36d6

Please sign in to comment.