[Logs onboarding] Removes dependency on apikey id in saved objects (#159535)

Closes #159381

Makes use of the auto-generated saved object ID to identify
observability onboarding state saved objects, so the API key id is never
persisted. In this change, the generated API key never has an explicit
association with the saved object for the onboarding flow.
This commit is contained in:
Oliver Gupte 2023-06-14 10:20:47 -04:00 committed by GitHub
parent 85c5b871de
commit 2bab34a8df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 83 additions and 61 deletions

View file

@ -120,7 +120,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"ml-module": "2225cbb4bd508ea5f69db4b848be9d8a74b60198",
"ml-trained-model": "482195cefd6b04920e539d34d7356d22cb68e4f3",
"monitoring-telemetry": "5d91bf75787d9d4dd2fae954d0b3f76d33d2e559",
"observability-onboarding-state": "c2a7439293913d69cc286a8f8f9885bc2dd9682f",
"observability-onboarding-state": "55b112d6a33fedb7c1e4fec4da768d2bcc5fadc2",
"osquery-manager-usage-metric": "983bcbc3b7dda0aad29b20907db233abba709bcc",
"osquery-pack": "6ab4358ca4304a12dcfc1777c8135b75cffb4397",
"osquery-pack-asset": "b14101d3172c4b60eb5404696881ce5275c84152",

View file

@ -3,13 +3,14 @@
API_KEY_ENCODED=$1
API_ENDPOINT=$2
ELASTIC_AGENT_VERSION=$3
AUTO_DOWNLOAD_CONFIG=$4
ONBOARDING_ID=$4
AUTO_DOWNLOAD_CONFIG=$5
updateStepProgress() {
local STEPNAME="$1"
local STATUS="$2" # "incomplete" | "complete" | "disabled" | "loading" | "warning" | "danger" | "current"
curl --request GET \
--url "${API_ENDPOINT}/custom_logs/step/${STEPNAME}?status=${STATUS}" \
--url "${API_ENDPOINT}/custom_logs/${ONBOARDING_ID}/step/${STEPNAME}?status=${STATUS}" \
--header "Authorization: ApiKey ${API_KEY_ENCODED}" \
--header "Content-Type: application/json" \
--header "kbn-xsrf: true" \
@ -93,7 +94,7 @@ downloadElasticAgentConfig() {
echo "Downloading elastic-agent.yml"
updateStepProgress "ea-config" "loading"
curl --request GET \
--url "${API_ENDPOINT}/elastic_agent/config" \
--url "${API_ENDPOINT}/elastic_agent/config?id=${ONBOARDING_ID}" \
--header "Authorization: ApiKey ${API_KEY_ENCODED}" \
--header "Content-Type: application/json" \
--header "kbn-xsrf: true" \

View file

@ -43,7 +43,7 @@ export function InstallElasticAgent() {
const wizardState = getState();
const [elasticAgentPlatform, setElasticAgentPlatform] =
useState<ElasticAgentPlatform>('linux-tar');
const [apiKeyId, setApiKeyId] = useState('');
const [onboardingId, setOnboardingId] = useState('');
function onInspect() {
goToStep('inspect');
@ -119,11 +119,16 @@ export function InstallElasticAgent() {
headers: {
authorization: `ApiKey ${installShipperSetup?.apiKeyEncoded}`,
},
params: {
query: { id: installShipperSetup?.id ?? '' },
},
};
return callApi(
'GET /api/observability_onboarding/elastic_agent/config 2023-05-24',
installShipperSetup?.apiKeyEncoded ? options : {}
installShipperSetup?.apiKeyEncoded
? options
: { params: options.params }
);
}
},
@ -131,8 +136,8 @@ export function InstallElasticAgent() {
);
useEffect(() => {
setApiKeyId(installShipperSetup?.apiKeyId ?? '');
}, [installShipperSetup?.apiKeyId]);
setOnboardingId(installShipperSetup?.id ?? '');
}, [installShipperSetup?.id]);
const apiKeyEncoded = installShipperSetup?.apiKeyEncoded;
@ -142,16 +147,14 @@ export function InstallElasticAgent() {
refetch: refetchProgress,
} = useFetcher(
(callApi) => {
if (CurrentStep === InstallElasticAgent && apiKeyId) {
if (CurrentStep === InstallElasticAgent && onboardingId) {
return callApi(
'GET /internal/observability_onboarding/custom_logs/progress',
{
params: { query: { apiKeyId } },
}
'GET /internal/observability_onboarding/custom_logs/{id}/progress',
{ params: { path: { id: onboardingId } } }
);
}
},
[apiKeyId]
[onboardingId]
);
const progressSucceded = progressStatus === FETCH_STATUS.SUCCESS;
@ -352,6 +355,7 @@ export function InstallElasticAgent() {
scriptDownloadUrl: setup?.scriptDownloadUrl,
elasticAgentVersion: setup?.elasticAgentVersion,
autoDownloadConfig: wizardState.autoDownloadConfig,
onboardingId,
})}
</EuiCodeBlock>
<EuiSpacer size="m" />
@ -571,6 +575,7 @@ function getInstallShipperCommand({
scriptDownloadUrl = '$SCRIPT_DOWNLOAD_URL',
elasticAgentVersion = '$ELASTIC_AGENT_VERSION',
autoDownloadConfig = false,
onboardingId = '$ONBOARDING_ID',
}: {
elasticAgentPlatform: ElasticAgentPlatform;
apiKeyEncoded: string | undefined;
@ -578,12 +583,13 @@ function getInstallShipperCommand({
scriptDownloadUrl: string | undefined;
elasticAgentVersion: string | undefined;
autoDownloadConfig: boolean;
onboardingId: string | undefined;
}) {
const setupScriptFilename = 'standalone_agent_setup.sh';
const PLATFORM_COMMAND: Record<ElasticAgentPlatform, string> = {
'linux-tar': oneLine`
curl ${scriptDownloadUrl} -o ${setupScriptFilename} &&
sudo bash ${setupScriptFilename} ${apiKeyEncoded} ${apiEndpoint} ${elasticAgentVersion} ${
sudo bash ${setupScriptFilename} ${apiKeyEncoded} ${apiEndpoint} ${elasticAgentVersion} ${onboardingId} ${
autoDownloadConfig ? 'autoDownloadConfig=1' : ''
}
`,

View file

@ -14,15 +14,15 @@ import {
export async function getObservabilityOnboardingState({
savedObjectsClient,
apiKeyId,
savedObjectId,
}: {
savedObjectsClient: SavedObjectsClientContract;
apiKeyId: string;
savedObjectId: string;
}): Promise<SavedObservabilityOnboardingState | undefined> {
try {
const result = await savedObjectsClient.get<ObservabilityOnboardingState>(
OBSERVABILITY_ONBOARDING_STATE_SAVED_OBJECT_TYPE,
apiKeyId
savedObjectId
);
const { id, updated_at: updatedAt, attributes } = result;
return {

View file

@ -6,7 +6,6 @@
*/
import * as t from 'io-ts';
import { getAuthenticationAPIKey } from '../../lib/get_authentication_api_key';
import { ObservabilityOnboardingState } from '../../saved_objects/observability_onboarding_status';
import { createObservabilityOnboardingServerRoute } from '../create_observability_onboarding_server_route';
import { createShipperApiKey } from './api_key/create_shipper_api_key';
@ -50,7 +49,9 @@ const installShipperSetupRoute = createObservabilityOnboardingServerRoute({
const coreStart = await core.start();
const kibanaUrl =
plugins.cloud?.setup?.kibanaUrl ?? getFallbackUrls(coreStart).kibanaUrl;
core.setup.http.basePath.publicBaseUrl ?? // priority given to server.publicBaseUrl
plugins.cloud?.setup?.kibanaUrl ?? // then cloud id
getFallbackUrls(coreStart).kibanaUrl; // falls back to local network binding
const scriptDownloadUrl = `${kibanaUrl}/plugins/observabilityOnboarding/assets/standalone_agent_setup.sh`;
const apiEndpoint = `${kibanaUrl}/api/observability_onboarding`;
@ -71,10 +72,7 @@ const createApiKeyRoute = createObservabilityOnboardingServerRoute({
state: t.record(t.string, t.unknown),
}),
}),
async handler(resources): Promise<{
apiKeyId: string;
apiKeyEncoded: string;
}> {
async handler(resources): Promise<{ apiKeyEncoded: string; id: string }> {
const {
context,
params: {
@ -87,32 +85,29 @@ const createApiKeyRoute = createObservabilityOnboardingServerRoute({
const {
elasticsearch: { client },
} = await context.core;
const { id: apiKeyId, encoded: apiKeyEncoded } = await createShipperApiKey(
const { encoded: apiKeyEncoded } = await createShipperApiKey(
client.asCurrentUser,
name
);
const savedObjectsClient = coreStart.savedObjects.getScopedClient(request);
await saveObservabilityOnboardingState({
const { id } = await saveObservabilityOnboardingState({
savedObjectsClient,
apiKeyId,
observabilityOnboardingState: { state } as ObservabilityOnboardingState,
});
return {
apiKeyId,
apiKeyEncoded,
};
return { apiKeyEncoded, id };
},
});
const stepProgressUpdateRoute = createObservabilityOnboardingServerRoute({
endpoint:
'GET /api/observability_onboarding/custom_logs/step/{name} 2023-05-24',
'GET /api/observability_onboarding/custom_logs/{id}/step/{name} 2023-05-24',
options: { tags: [] },
params: t.type({
path: t.type({
id: t.string,
name: t.string,
}),
query: t.type({
@ -122,13 +117,11 @@ const stepProgressUpdateRoute = createObservabilityOnboardingServerRoute({
async handler(resources): Promise<object> {
const {
params: {
path: { name },
path: { id, name },
query: { status },
},
request,
core,
} = resources;
const authApiKey = getAuthenticationAPIKey(request);
const coreStart = await core.start();
const savedObjectsClient =
coreStart.savedObjects.createInternalRepository();
@ -136,7 +129,7 @@ const stepProgressUpdateRoute = createObservabilityOnboardingServerRoute({
const savedObservabilityOnboardingState =
await getObservabilityOnboardingState({
savedObjectsClient,
apiKeyId: authApiKey?.apiKeyId as string,
savedObjectId: id,
});
if (!savedObservabilityOnboardingState) {
@ -146,11 +139,15 @@ const stepProgressUpdateRoute = createObservabilityOnboardingServerRoute({
};
}
const { id, updatedAt, ...observabilityOnboardingState } =
savedObservabilityOnboardingState;
const {
id: savedObjectId,
updatedAt,
...observabilityOnboardingState
} = savedObservabilityOnboardingState;
await saveObservabilityOnboardingState({
savedObjectsClient,
apiKeyId: authApiKey?.apiKeyId as string,
savedObjectId,
observabilityOnboardingState: {
...observabilityOnboardingState,
progress: {
@ -164,17 +161,17 @@ const stepProgressUpdateRoute = createObservabilityOnboardingServerRoute({
});
const getProgressRoute = createObservabilityOnboardingServerRoute({
endpoint: 'GET /internal/observability_onboarding/custom_logs/progress',
endpoint: 'GET /internal/observability_onboarding/custom_logs/{id}/progress',
options: { tags: [] },
params: t.type({
query: t.type({
apiKeyId: t.string,
path: t.type({
id: t.string,
}),
}),
async handler(resources): Promise<{ progress: Record<string, string> }> {
const {
params: {
query: { apiKeyId },
path: { id },
},
core,
request,
@ -185,7 +182,7 @@ const getProgressRoute = createObservabilityOnboardingServerRoute({
const savedObservabilityOnboardingState =
(await getObservabilityOnboardingState({
savedObjectsClient,
apiKeyId,
savedObjectId: id,
})) || null;
const progress = { ...savedObservabilityOnboardingState?.progress };

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { SavedObjectsClientContract } from '@kbn/core/server';
import { SavedObjectsClientContract, SavedObject } from '@kbn/core/server';
import {
OBSERVABILITY_ONBOARDING_STATE_SAVED_OBJECT_TYPE,
ObservabilityOnboardingState,
@ -15,26 +15,33 @@ import {
interface Options {
savedObjectsClient: SavedObjectsClientContract;
observabilityOnboardingState: ObservabilityOnboardingState;
apiKeyId: string;
savedObjectId?: string;
}
export async function saveObservabilityOnboardingState({
savedObjectsClient,
observabilityOnboardingState,
apiKeyId,
savedObjectId,
}: Options): Promise<SavedObservabilityOnboardingState> {
const {
id,
attributes,
updated_at: updatedAt,
} = await savedObjectsClient.update<ObservabilityOnboardingState>(
OBSERVABILITY_ONBOARDING_STATE_SAVED_OBJECT_TYPE,
apiKeyId,
observabilityOnboardingState,
{ upsert: observabilityOnboardingState }
);
let savedObject: Omit<
SavedObject<ObservabilityOnboardingState>,
'attributes' | 'references'
>;
if (savedObjectId) {
savedObject = await savedObjectsClient.update<ObservabilityOnboardingState>(
OBSERVABILITY_ONBOARDING_STATE_SAVED_OBJECT_TYPE,
savedObjectId,
observabilityOnboardingState
);
} else {
savedObject = await savedObjectsClient.create<ObservabilityOnboardingState>(
OBSERVABILITY_ONBOARDING_STATE_SAVED_OBJECT_TYPE,
observabilityOnboardingState
);
}
const { id, updated_at: updatedAt } = savedObject;
return {
id,
...(attributes as ObservabilityOnboardingState),
...observabilityOnboardingState,
updatedAt: updatedAt ? Date.parse(updatedAt) : 0,
};
}

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import * as t from 'io-ts';
import { getAuthenticationAPIKey } from '../../lib/get_authentication_api_key';
import { createObservabilityOnboardingServerRoute } from '../create_observability_onboarding_server_route';
import { getObservabilityOnboardingState } from '../custom_logs/get_observability_onboarding_state';
@ -13,9 +14,19 @@ import { getFallbackUrls } from '../custom_logs/get_fallback_urls';
const generateConfig = createObservabilityOnboardingServerRoute({
endpoint: 'GET /api/observability_onboarding/elastic_agent/config 2023-05-24',
params: t.type({
query: t.type({ id: t.string }),
}),
options: { tags: [] },
async handler(resources): Promise<string> {
const { core, plugins, request } = resources;
const {
params: {
query: { id },
},
core,
plugins,
request,
} = resources;
const authApiKey = getAuthenticationAPIKey(request);
const coreStart = await core.start();
@ -28,7 +39,7 @@ const generateConfig = createObservabilityOnboardingServerRoute({
const savedState = await getObservabilityOnboardingState({
savedObjectsClient,
apiKeyId: authApiKey?.apiKeyId ?? '',
savedObjectId: id,
});
const yaml = generateYml({

View file

@ -30,7 +30,7 @@ export interface SavedObservabilityOnboardingState
export const observabilityOnboardingState: SavedObjectsType = {
name: OBSERVABILITY_ONBOARDING_STATE_SAVED_OBJECT_TYPE,
hidden: false,
namespaceType: 'multiple',
namespaceType: 'agnostic',
mappings: {
properties: {
state: { type: 'object', dynamic: false },