Skip to content

Commit

Permalink
Merge branch '8.x' into ua/security-soln-logsdb-callout
Browse files Browse the repository at this point in the history
  • Loading branch information
jloleysens authored Feb 4, 2025
2 parents 6bcd2c1 + 2f94350 commit 1d4f0ca
Show file tree
Hide file tree
Showing 205 changed files with 32,336 additions and 1,416 deletions.
2 changes: 1 addition & 1 deletion .buildkite/scripts/steps/console_definitions_sync.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ main () {
git config --global user.name "$KIBANA_MACHINE_USERNAME"
git config --global user.email '[email protected]'

PR_TITLE="[Console] Update console definitions (${branch_name})"
PR_TITLE="[Console] Update console definitions (${BUILDKITE_BRANCH})"
PR_BODY='This PR updates the console definitions to match the latest ones from the @elastic/elasticsearch-specification repo.'

# Check if a PR already exists
Expand Down
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,7 @@ x-pack/platform/packages/private/ml/inference_integration_flyout @elastic/ml-ui
x-pack/platform/packages/shared/ai-infra/inference-common @elastic/appex-ai-infra
x-pack/platform/plugins/shared/inference_endpoint @elastic/ml-ui
x-pack/platform/packages/shared/kbn-inference-endpoint-ui-common @elastic/response-ops @elastic/appex-ai-infra @elastic/obs-ai-assistant @elastic/security-generative-ai
x-pack/platform/packages/shared/ai-infra/inference-langchain @elastic/appex-ai-infra
x-pack/platform/plugins/shared/inference @elastic/appex-ai-infra
x-pack/platform/packages/private/kbn-infra-forge @elastic/obs-ux-management-team
x-pack/solutions/observability/plugins/infra @elastic/obs-ux-logs-team @elastic/obs-ux-infra_services-team
Expand Down
17 changes: 17 additions & 0 deletions dev_docs/key_concepts/api_authorization.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,23 @@ router.get({
}, handler);
```

### Naming conventions for privileges
1. **Privilege should start with a valid `ApiOperation`**:
- **Valid operations**: `manage`, `read`, `update`, `delete`, `create`.
- Use the corresponding methods from the `ApiPrivileges` utility class: `ApiPrivileges.manage`, `ApiPrivileges.read`, etc.
2. **Use `_` as the separator** between the operation and the subject.

**Examples**:
Incorrect privilege names ❌
- `read-entity-a`: Uses `-` instead of `_`.
- `delete_entity-a`: Mixes `_` and `-`.
- `entity_manage`: Places the subject name before the operation.

Correct privilege names ✅
- `read_entity_a`
- `delete_entity_a`
- `manage_entity`

### Configuring operator and superuser privileges
We have two special predefined privilege sets that can be used in security configuration:
1. Operator
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,7 @@
"@kbn/inference-common": "link:x-pack/platform/packages/shared/ai-infra/inference-common",
"@kbn/inference-endpoint-plugin": "link:x-pack/platform/plugins/shared/inference_endpoint",
"@kbn/inference-endpoint-ui-common": "link:x-pack/platform/packages/shared/kbn-inference-endpoint-ui-common",
"@kbn/inference-langchain": "link:x-pack/platform/packages/shared/ai-infra/inference-langchain",
"@kbn/inference-plugin": "link:x-pack/platform/plugins/shared/inference",
"@kbn/inference_integration_flyout": "link:x-pack/platform/packages/private/ml/inference_integration_flyout",
"@kbn/infra-forge": "link:x-pack/platform/packages/private/kbn-infra-forge",
Expand Down Expand Up @@ -1233,7 +1234,7 @@
"react-popper-tooltip": "^4.4.2",
"react-recompose": "^0.33.0",
"react-redux": "^7.2.8",
"react-reverse-portal": "^2.1.2",
"react-reverse-portal": "^2.2.0",
"react-router": "^5.3.4",
"react-router-config": "^5.1.1",
"react-router-dom": "^5.3.4",
Expand Down Expand Up @@ -1304,7 +1305,8 @@
"yaml": "^2.5.1",
"yauzl": "^2.10.0",
"yazl": "^2.5.1",
"zod": "^3.22.3"
"zod": "^3.22.3",
"zod-to-json-schema": "^3.23.0"
},
"devDependencies": {
"@apidevtools/swagger-parser": "^10.1.1",
Expand Down Expand Up @@ -1877,8 +1879,7 @@
"xml-crypto": "^6.0.0",
"xmlbuilder": "13.0.2",
"yargs": "^15.4.1",
"yarn-deduplicate": "^6.0.2",
"zod-to-json-schema": "^3.23.0"
"yarn-deduplicate": "^6.0.2"
},
"packageManager": "[email protected]"
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ export class ApmSynthtraceKibanaClient {
);
}

// Add support for 7.x stack as latest version is available under different node
if (responseJson.response && responseJson.response.latestVersion) {
return responseJson.response.latestVersion as string;
}

return responseJson.item.latestVersion as string;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

const ts = require('typescript');
const path = require('path');

function getImportedVariableValue(context, name, propertyName) {
const parent = context
.getAncestors()
.find((ancestor) => ['BlockStatement', 'Program'].includes(ancestor.type));

if (!parent) return;

const importDeclaration = parent.body.find(
(statement) =>
statement.type === 'ImportDeclaration' &&
statement.specifiers.some((specifier) => specifier.local.name === name)
);

if (!importDeclaration) return;

const absoluteImportPath = require.resolve(importDeclaration.source.value, {
paths: [path.dirname(context.getFilename())],
});

const program = ts.createProgram([absoluteImportPath], {});
const sourceFile = program.getSourceFile(absoluteImportPath);

if (!sourceFile) return null;

const checker = program.getTypeChecker();
const symbols = checker.getExportsOfModule(sourceFile.symbol);
const symbol = symbols.find((s) => s.name === name);

if (!symbol) return null;

if (propertyName) {
const currentSymbol = checker.getTypeOfSymbolAtLocation(symbol, sourceFile);
const property = currentSymbol.getProperty(propertyName);

if (ts.isStringLiteral(property.valueDeclaration.initializer)) {
return property.valueDeclaration.initializer.text;
}

return null;
}

const initializer = symbol?.valueDeclaration?.initializer;

if (ts.isStringLiteral(initializer)) {
return initializer.text;
}

return null;
}

function validatePrivilegesNode(context, privilegesNode, scopedVariables) {
['all', 'read'].forEach((privilegeType) => {
const privilege = privilegesNode.value.properties.find(
(prop) =>
prop.key && prop.key.name === privilegeType && prop.value.type === 'ObjectExpression'
);

if (!privilege) return;

const apiProperty = privilege.value.properties.find(
(prop) => prop.key && prop.key.name === 'api' && prop.value.type === 'ArrayExpression'
);

if (!apiProperty) return;

apiProperty.value.elements.forEach((element) => {
let valueToCheck = null;

if (element.type === 'Literal' && typeof element.value === 'string') {
valueToCheck = element.value;
} else if (element.type === 'Identifier') {
valueToCheck = scopedVariables.has(element.name)
? scopedVariables.get(element.name)
: getImportedVariableValue(context, element.name);
} else if (element.type === 'MemberExpression') {
valueToCheck = getImportedVariableValue(
context,
element.object.name,
element.property.name
);
}

if (valueToCheck) {
const isValid = /^(manage|create|update|delete|read)/.test(valueToCheck);
const usesValidSeparator = /^[a-z0-9_]+$/.test(valueToCheck);
let method = 'manage';

if (valueToCheck.includes('read')) {
method = 'read';
}

if (valueToCheck.includes('create') || valueToCheck.includes('copy')) {
method = 'create';
}

if (valueToCheck.includes('delete')) {
method = 'delete';
}

if (valueToCheck.includes('update')) {
method = 'update';
}

if (!isValid) {
return context.report({
node: element,
message: `API privilege '${valueToCheck}' should start with [manage|create|update|delete|read] or use ApiPrivileges.${method} instead`,
});
}

if (!usesValidSeparator) {
return context.report({
node: element,
message: `API privilege '${valueToCheck}' should use '_' as a separator`,
});
}
}
});
});
}

module.exports = {
meta: {
type: 'problem',
docs: {
description: 'Ensure API privileges in registerKibanaFeature call follow naming conventions',
category: 'Best Practices',
recommended: true,
},
schema: [],
},

create(context) {
return {
CallExpression(node) {
const isRegisterKibanaFeatureCall =
node.callee.type === 'MemberExpression' &&
node.callee.property.name === 'registerKibanaFeature' &&
((node.callee.object.type === 'MemberExpression' &&
node.callee.object.property.name === 'features') ||
node.callee.object.name === 'features');

if (!isRegisterKibanaFeatureCall) return;

const scopedVariables = new Map();

const sourceCode = context.getSourceCode();

const parent = sourceCode
.getAncestors(node)
.find((ancestor) => ['BlockStatement', 'Program'].includes(ancestor.type));

if (parent) {
parent.body.forEach((statement) => {
if (statement.type === 'VariableDeclaration') {
statement.declarations.forEach((declaration) => {
if (
declaration.id.type === 'Identifier' &&
declaration.init &&
declaration.init.type === 'Literal' &&
typeof declaration.init.value === 'string'
) {
scopedVariables.set(declaration.id.name, declaration.init.value);
}
});
}
});
}

const [feature] = node.arguments;
if (feature?.type === 'ObjectExpression') {
const privilegesProperty = feature.properties.find(
(prop) =>
prop.key && prop.key.name === 'privileges' && prop.value.type === 'ObjectExpression'
);

if (!privilegesProperty) return;

return validatePrivilegesNode(context, privilegesProperty, scopedVariables);
}
},
ExportNamedDeclaration(node) {
if (
node.declaration?.type !== 'VariableDeclaration' ||
!node.declaration.declarations?.length
) {
return;
}

node.declaration.declarations.forEach((declaration) => {
if (declaration.init && declaration.init.type === 'ObjectExpression') {
if (
!['id', 'name', 'privileges', 'scope', 'category'].every((key) =>
declaration.init.properties.find((prop) => prop.key?.name === key)
)
) {
return;
}

const privilegesProperty = declaration.init.properties.find(
(prop) =>
prop.key && prop.key.name === 'privileges' && prop.value.type === 'ObjectExpression'
);

validatePrivilegesNode(context, privilegesProperty, new Map());
}
});
},
};
},
};
Loading

0 comments on commit 1d4f0ca

Please sign in to comment.