-
Notifications
You must be signed in to change notification settings - Fork 669
Generate an integrations package from a release #983
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
52db8e2
b3676ba
3f58332
61ec159
0c541fc
4117d74
4546bfc
1756c94
9766145
49b5c7e
4ee618d
c2e22d1
cbc569a
d2c90f3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ | |
| RELEASE_DIR = get_path("releases") | ||
| PACKAGE_FILE = get_etc_path('packages.yml') | ||
| NOTICE_FILE = get_path('NOTICE.txt') | ||
| CHANGELOG_FILE = Path(get_etc_path('rules-changelog.json')) | ||
|
|
||
|
|
||
| def filter_rule(rule: Rule, config_filter: dict, exclude_fields: dict) -> bool: | ||
|
|
@@ -149,13 +150,15 @@ def manage_versions(rules: list, deprecated_rules: list = None, current_versions | |
| class Package(object): | ||
| """Packaging object for siem rules and releases.""" | ||
|
|
||
| def __init__(self, rules, name, deprecated_rules=None, release=False, current_versions=None, min_version=None, | ||
| max_version=None, update_version_lock=False, verbose=True): | ||
| def __init__(self, rules: List[Rule], name, deprecated_rules: List[Rule] = None, release=False, | ||
| current_versions: dict = None, min_version: int = None, max_version: int = None, | ||
| update_version_lock=False, registry_data: dict = None, verbose=True): | ||
|
brokensound77 marked this conversation as resolved.
Outdated
|
||
| """Initialize a package.""" | ||
| self.rules: List[Rule] = [r.copy() for r in rules] | ||
| self.rules = [r.copy() for r in rules] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like type inspection has figured it out. I think we could annotate the |
||
| self.name = name | ||
| self.deprecated_rules: List[Rule] = [r.copy() for r in deprecated_rules or []] | ||
| self.deprecated_rules = [r.copy() for r in deprecated_rules or []] | ||
| self.release = release | ||
| self.registry_data = registry_data or {} | ||
|
|
||
| self.changed_rule_ids, self.new_rules_ids, self.removed_rule_ids = self._add_versions(current_versions, | ||
| update_version_lock, | ||
|
|
@@ -256,6 +259,9 @@ def save(self, verbose=True): | |
| self._package_kibana_index_file(rules_dir) | ||
|
|
||
| if self.release: | ||
| if self.registry_data: | ||
|
brokensound77 marked this conversation as resolved.
Outdated
|
||
| self._generate_registry_package(save_dir) | ||
|
|
||
| self.save_release_files(extras_dir, self.changed_rule_ids, self.new_rules_ids, self.removed_rule_ids) | ||
|
|
||
| # zip all rules only and place in extras | ||
|
|
@@ -460,6 +466,39 @@ def generate_xslx(self, path): | |
| doc.populate() | ||
| doc.close() | ||
|
|
||
| def _generate_registry_package(self, save_dir): | ||
| """Generate the artifact for the oob package-storage.""" | ||
| from .schemas.registry_package import get_manifest | ||
|
|
||
| assert self.registry_data | ||
|
brokensound77 marked this conversation as resolved.
Outdated
|
||
|
|
||
| registry_manifest = get_manifest(self.registry_data['format_version']) | ||
| manifest = registry_manifest.Schema().load(self.registry_data) | ||
|
|
||
| package_dir = Path(save_dir).joinpath(manifest.version) | ||
| docs_dir = package_dir.joinpath('docs') | ||
| rules_dir = package_dir.joinpath('kibana', 'rules') | ||
|
brokensound77 marked this conversation as resolved.
Outdated
|
||
|
|
||
| docs_dir.mkdir(parents=True) | ||
| rules_dir.mkdir(parents=True) | ||
|
brokensound77 marked this conversation as resolved.
|
||
|
|
||
| manifest_file = package_dir.joinpath('manifest.yml') | ||
| readme_file = docs_dir.joinpath('README.md') | ||
|
|
||
| manifest_file.write_text(json.dumps(manifest.dump(), indent=2, sort_keys=True)) | ||
| shutil.copyfile(CHANGELOG_FILE, str(rules_dir.joinpath('CHANGELOG.json'))) | ||
|
|
||
| for rule in self.rules: | ||
| rule.save(new_path=str(rules_dir.joinpath(f'rule-{rule.id}.json'))) | ||
|
|
||
| readme_text = '# Detection rules\n' | ||
| readme_text += '\n' | ||
| readme_text += 'The detection rules package is a non-integration package to store all the rules and ' | ||
|
brokensound77 marked this conversation as resolved.
Outdated
|
||
| readme_text += 'dependencies (e.g. ML jobs) for the detection engine within the Elastic Security application.\n' | ||
| readme_text += '\n' | ||
|
|
||
| readme_file.write_text(readme_text) | ||
|
|
||
| def bump_versions(self, save_changes=False, current_versions=None): | ||
| """Bump the versions of all production rules included in a release and optionally save changes.""" | ||
| return manage_versions(self.rules, current_versions=current_versions, save_changes=save_changes) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| # or more contributor license agreements. Licensed under the Elastic License; | ||
| # you may not use this file except in compliance with the Elastic License. | ||
|
|
||
| """Custom shared definitions for schemas.""" | ||
|
brokensound77 marked this conversation as resolved.
|
||
|
|
||
| from typing import ClassVar, Type | ||
|
|
||
| import marshmallow | ||
| import marshmallow_dataclass | ||
| from marshmallow_dataclass import NewType | ||
| from marshmallow import validate | ||
|
|
||
|
|
||
| DATE_PATTERN = r'\d{4}/\d{2}/\d{2}' | ||
| MATURITY_LEVELS = ['development', 'experimental', 'beta', 'production', 'deprecated'] | ||
| OS_OPTIONS = ['windows', 'linux', 'macos', 'solaris'] | ||
| PR_PATTERN = r'^$|\d+' | ||
| SHA256_PATTERN = r'[a-fA-F0-9]{64}' | ||
| UUID_PATTERN = r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' | ||
|
|
||
| _version = r'\d+\.\d+(\.\d+[\w-]*)*' | ||
| CONDITION_VERSION_PATTERN = rf'^\^{_version}$' | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think we'll loosen this up as we go. might move from
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good to know - can always expand it here |
||
| VERSION_PATTERN = f'^{_version}$' | ||
| VERSION_W_MASTER_PATTERN = f'{VERSION_PATTERN}|^master$' | ||
|
brokensound77 marked this conversation as resolved.
Outdated
|
||
|
|
||
| ConditionSemVer = NewType('ConditionSemVer', str, validate=validate.Regexp(CONDITION_VERSION_PATTERN)) | ||
| Date = NewType('Date', str, validate=validate.Regexp(DATE_PATTERN)) | ||
|
rw-access marked this conversation as resolved.
|
||
| SemVer = NewType('SemVer', str, validate=validate.Regexp(VERSION_PATTERN)) | ||
| Sha256 = NewType('Sha256', str, validate=validate.Regexp(SHA256_PATTERN)) | ||
| Uuid = NewType('Uuid', str, validate=validate.Regexp(UUID_PATTERN)) | ||
|
rw-access marked this conversation as resolved.
Outdated
|
||
|
|
||
|
|
||
| @marshmallow_dataclass.dataclass | ||
| class BaseMarshmallowDataclass: | ||
| """Base marshmallow dataclass configs.""" | ||
|
|
||
| class Meta: | ||
| ordered = True | ||
|
|
||
| Schema: ClassVar[Type[marshmallow.Schema]] = marshmallow.Schema | ||
|
|
||
| def dump(self) -> dict: | ||
| return self.Schema().dump(self) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| # Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| # or more contributor license agreements. Licensed under the Elastic License; | ||
| # you may not use this file except in compliance with the Elastic License. | ||
|
|
||
| """Definitions for packages destined for the registry.""" | ||
|
|
||
| import dataclasses | ||
| from typing import Dict, Union, Type | ||
|
|
||
| import marshmallow_dataclass | ||
| from marshmallow import validate | ||
|
|
||
| from .definitions import BaseMarshmallowDataclass, ConditionSemVer, SemVer | ||
|
|
||
|
|
||
| @marshmallow_dataclass.dataclass | ||
|
brokensound77 marked this conversation as resolved.
Outdated
|
||
| class BaseManifest(BaseMarshmallowDataclass): | ||
|
brokensound77 marked this conversation as resolved.
Outdated
|
||
| """Base class for registry packages.""" | ||
|
|
||
| conditions: Dict[str, ConditionSemVer] | ||
| version: SemVer | ||
| format_version: SemVer | ||
|
|
||
| categories: list = dataclasses.field(default_factory=lambda: ['security'].copy()) | ||
|
brokensound77 marked this conversation as resolved.
Outdated
|
||
| description: str = 'Rules for the detection engine in the Security application.' | ||
| icons: list = dataclasses.field(default_factory=list) | ||
|
brokensound77 marked this conversation as resolved.
Outdated
|
||
| license: str = 'basic' | ||
| name: str = 'detection_rules' | ||
| owner: dict = dataclasses.field(default_factory=lambda: dict(github='elastic/protections').copy()) | ||
| policy_templates: list = dataclasses.field(default_factory=list) | ||
| release: str = 'experimental' | ||
| screenshots: list = dataclasses.field(default_factory=list) | ||
| title: str = 'Detection rules' | ||
| type: str = 'rules' | ||
|
brokensound77 marked this conversation as resolved.
Outdated
|
||
|
|
||
|
|
||
| @marshmallow_dataclass.dataclass | ||
| class ManifestV1Dot0(BaseManifest): | ||
|
brokensound77 marked this conversation as resolved.
Outdated
|
||
| """Integrations registry package schema.""" | ||
|
|
||
| format_version: SemVer = dataclasses.field(metadata=dict(validate=validate.Equal('1.0.0')), default='1.0.0') | ||
|
|
||
|
|
||
| manifests = [ | ||
| ManifestV1Dot0 | ||
| ] | ||
|
|
||
|
|
||
| def get_manifest(format_version: str) -> Union[Type[ManifestV1Dot0]]: | ||
| """Retrieve a manifest class by format_version.""" | ||
| for manifest in manifests: | ||
| if manifest.format_version == format_version: | ||
| return manifest | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| { | ||
|
brokensound77 marked this conversation as resolved.
Outdated
|
||
| "changelog": {} | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.