diff --git a/integrations/anchore-falco/Dockerfile b/integrations/anchore-falco/Dockerfile new file mode 100644 index 00000000..8e491473 --- /dev/null +++ b/integrations/anchore-falco/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3-stretch + +RUN pip install pipenv + +WORKDIR /app + +ADD Pipfile /app/Pipfile +ADD Pipfile.lock /app/Pipfile.lock +RUN pipenv install --system --deploy + +ADD . /app + +CMD ["python", "main.py"] diff --git a/integrations/anchore-falco/Pipfile b/integrations/anchore-falco/Pipfile new file mode 100644 index 00000000..12ad52f7 --- /dev/null +++ b/integrations/anchore-falco/Pipfile @@ -0,0 +1,16 @@ +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + +[dev-packages] +doublex-expects = "==0.7.0rc2" +doublex = "*" +mamba = "*" +expects = "*" + +[packages] +requests = "*" + +[requires] +python_version = "3.6" diff --git a/integrations/anchore-falco/Pipfile.lock b/integrations/anchore-falco/Pipfile.lock new file mode 100644 index 00000000..dd3a43ad --- /dev/null +++ b/integrations/anchore-falco/Pipfile.lock @@ -0,0 +1,156 @@ +{ + "_meta": { + "hash": { + "sha256": "f2737a14e8f562cf355e13ae09f1eed0f80415effd2aa01b86125e94523da345" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.6" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7", + "sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0" + ], + "version": "==2018.4.16" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "idna": { + "hashes": [ + "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", + "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" + ], + "version": "==2.7" + }, + "requests": { + "hashes": [ + "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1", + "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a" + ], + "index": "pypi", + "version": "==2.19.1" + }, + "urllib3": { + "hashes": [ + "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", + "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" + ], + "version": "==1.23" + } + }, + "develop": { + "args": { + "hashes": [ + "sha256:a785b8d837625e9b61c39108532d95b85274acd679693b71ebb5156848fcf814" + ], + "version": "==0.1.0" + }, + "clint": { + "hashes": [ + "sha256:05224c32b1075563d0b16d0015faaf9da43aa214e4a2140e51f08789e7a4c5aa" + ], + "version": "==0.5.1" + }, + "coverage": { + "hashes": [ + "sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba", + "sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed", + "sha256:104ab3934abaf5be871a583541e8829d6c19ce7bde2923b2751e0d3ca44db60a", + "sha256:15b111b6a0f46ee1a485414a52a7ad1d703bdf984e9ed3c288a4414d3871dcbd", + "sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640", + "sha256:1c383d2ef13ade2acc636556fd544dba6e14fa30755f26812f54300e401f98f2", + "sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162", + "sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508", + "sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249", + "sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694", + "sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a", + "sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287", + "sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1", + "sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000", + "sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1", + "sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e", + "sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5", + "sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062", + "sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba", + "sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc", + "sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc", + "sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99", + "sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653", + "sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c", + "sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558", + "sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f", + "sha256:9e112fcbe0148a6fa4f0a02e8d58e94470fc6cb82a5481618fea901699bf34c4", + "sha256:ac4fef68da01116a5c117eba4dd46f2e06847a497de5ed1d64bb99a5fda1ef91", + "sha256:b8815995e050764c8610dbc82641807d196927c3dbed207f0a079833ffcf588d", + "sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9", + "sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd", + "sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d", + "sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6", + "sha256:e4d96c07229f58cb686120f168276e434660e4358cc9cf3b0464210b04913e77", + "sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80", + "sha256:f8a923a85cb099422ad5a2e345fe877bbc89a8a8b23235824a93488150e45f6e" + ], + "version": "==4.5.1" + }, + "doublex": { + "hashes": [ + "sha256:062af49d9e4148bc47b7512d3fdc8e145dea4671d074ffd54b2464a19d3757ab" + ], + "index": "pypi", + "version": "==1.8.4" + }, + "doublex-expects": { + "hashes": [ + "sha256:5421bd92319c77ccc5a81d595d06e9c9f7f670de342b33e8007a81e70f9fade8" + ], + "index": "pypi", + "version": "==0.7.0rc2" + }, + "expects": { + "hashes": [ + "sha256:37538d7b0fa9c0d53e37d07b0e8c07d89754d3deec1f0f8ed1be27f4f10363dd" + ], + "index": "pypi", + "version": "==0.8.0" + }, + "mamba": { + "hashes": [ + "sha256:63e70a8666039cf143a255000e23f29be4ea4b5b8169f2b053f94eb73a2ea9e2" + ], + "index": "pypi", + "version": "==0.9.3" + }, + "pyhamcrest": { + "hashes": [ + "sha256:6b672c02fdf7470df9674ab82263841ce8333fb143f32f021f6cb26f0e512420", + "sha256:7a4bdade0ed98c699d728191a058a60a44d2f9c213c51e2dd1e6fb42f2c6128a", + "sha256:8ffaa0a53da57e89de14ced7185ac746227a8894dbd5a3c718bf05ddbd1d56cd", + "sha256:bac0bea7358666ce52e3c6c85139632ed89f115e9af52d44b3c36e0bf8cf16a9", + "sha256:f30e9a310bcc1808de817a92e95169ffd16b60cbc5a016a49c8d0e8ababfae79" + ], + "version": "==1.9.0" + }, + "six": { + "hashes": [ + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + ], + "version": "==1.11.0" + } + } +} diff --git a/integrations/anchore-falco/README.md b/integrations/anchore-falco/README.md new file mode 100644 index 00000000..a6be7ff7 --- /dev/null +++ b/integrations/anchore-falco/README.md @@ -0,0 +1,89 @@ +# Create Falco rule from Anchore policy result + +This integration creates a rule for Sysdig Falco based on Anchore policy result. +So that when we will try to run an image which has a ```stop``` final action result +in Anchore, Falco will alert us. + +## Getting started + +### Prerequisites + +For running this integration you will need: + +* Python 3.6 +* pipenv +* An [anchore-engine](https://github.com/anchore/anchore-engine) running + +### Configuration + +This integration uses the [same environment variables that anchore-cli](https://github.com/anchore/anchore-cli#configuring-the-anchore-cli): + +* ANCHORE_CLI_USER: The user used to conect to anchore-engine. By default is ```admin``` +* ANCHORE_CLI_PASS: The password used to connect to anchore-engine. +* ANCHORE_CLI_URL: The url where anchore-engine listens. Make sure does not end with a slash. By default is ```http://localhost:8228/v1``` +* ANCHORE_CLI_SSL_VERIFY: Flag for enabling if HTTP client verifies SSL. By default is ```true``` + +### Running + +This is a Python program which generates a Falco rule based on anchore-engine +information: + +``` +pipenv run python main.py +``` + +And this will output something like: + + +```yaml +- macro: anchore_stop_policy_evaluation_containers + condition: container.image.id in ("8626492fecd368469e92258dfcafe055f636cb9cbc321a5865a98a0a6c99b8dd", "e86d9bb526efa0b0401189d8df6e3856d0320a3d20045c87b4e49c8a8bdb22c1") + +- rule: Run Anchore Containers with Stop Policy Evaluation + desc: Detect containers which does not receive a positive Policy Evaluation from Anchore Engine. + + condition: evt.type=execve and proc.vpid=1 and container and anchore_stop_policy_evaluation_containers + output: A stop policy evaluation container from anchore has started (%container.info image=%container.image) + priority: INFO + tags: [container] +``` + +You can save that output to ```/etc/falco/rules.d/anchore-integration-rules.yaml``` +and Falco will start checking this rule. + +As long as information in anchore-engine can change, it's a good idea to run this +integration **periodically** and keep the rule synchronized with anchore-engine +policy evaluation result. + +## Tests + +As long as there are contract tests with anchore-engine, it needs a working +anchore-engine and its environment variables. + +``` +pipenv install -d +pipenv run mamba --format=documentation +``` + +## Docker support + +### Build the image + +``` +docker build -t sysdig/anchore-falco . +``` + +### Running the image + +An image exists on DockerHub, its name is ```sysdig/anchore-falco```. + +So you can run directly with Docker: + +``` +docker run --rm -e ANCHORE_CLI_USER= \ + -e ANCHORE_CLI_PASS= \ + -e ANCHORE_CLI_URL=http://:8228/v1 \ + sysdig/anchore-falco +``` + +And this will output the Falco rule based on *custom-anchore-engine-host*. diff --git a/integrations/anchore-falco/actions.py b/integrations/anchore-falco/actions.py new file mode 100644 index 00000000..28031726 --- /dev/null +++ b/integrations/anchore-falco/actions.py @@ -0,0 +1,25 @@ +import string + +FALCO_RULE_TEMPLATE = string.Template(''' +- macro: anchore_stop_policy_evaluation_containers + condition: container.image.id in ($images) + +- rule: Run Anchore Containers with Stop Policy Evaluation + desc: Detect containers which does not receive a positive Policy Evaluation from Anchore Engine. + + condition: evt.type=execve and proc.vpid=1 and container and anchore_stop_policy_evaluation_containers + output: A stop policy evaluation container from anchore has started (%container.info image=%container.image) + priority: INFO + tags: [container] +''') + + +class CreateFalcoRuleFromAnchoreStopPolicyResults: + def __init__(self, anchore_client): + self._anchore_client = anchore_client + + def run(self): + images = self._anchore_client.get_images_with_policy_result('stop') + + images = ['"{}"'.format(image) for image in images] + return FALCO_RULE_TEMPLATE.substitute(images=', '.join(images)) diff --git a/integrations/anchore-falco/infrastructure.py b/integrations/anchore-falco/infrastructure.py new file mode 100644 index 00000000..33eabd52 --- /dev/null +++ b/integrations/anchore-falco/infrastructure.py @@ -0,0 +1,39 @@ +import requests + + +class AnchoreClient: + def __init__(self, user, password, url, ssl_verify): + self._user = user + self._password = password + self._url = url + self._ssl_verify = ssl_verify + + def get_images_with_policy_result(self, policy_result): + results = [] + for image in self._get_all_images(): + final_action = self._evaluate_image(image) + + if final_action == 'stop': + results.append(image['image_id']) + + return results + + def _get_all_images(self): + response = self._do_get_request(self._url + '/images') + return [ + { + 'image_id': image['image_detail'][0]['imageId'], + 'image_digest': image['image_detail'][0]['imageDigest'], + 'full_tag': image['image_detail'][0]['fulltag'] + } for image in response.json()] + + def _do_get_request(self, url): + return requests.get(url, + auth=(self._user, self._password), + verify=self._ssl_verify, + headers={'Content-Type': 'application/json'}) + + def _evaluate_image(self, image): + response = self._do_get_request(self._url + '/images/{}/check?tag={}'.format(image['image_digest'], image['full_tag'])) + if response.status_code == 200: + return response.json()[0][image['image_digest']][image['full_tag']][0]['detail']['result']['final_action'] diff --git a/integrations/anchore-falco/main.py b/integrations/anchore-falco/main.py new file mode 100644 index 00000000..a8c59b31 --- /dev/null +++ b/integrations/anchore-falco/main.py @@ -0,0 +1,21 @@ +import os + +import actions, infrastructure + + +def main(): + anchore_client = infrastructure.AnchoreClient( + os.environ.get('ANCHORE_CLI_USER', 'admin'), + os.environ['ANCHORE_CLI_PASS'], + os.environ.get('ANCHORE_CLI_URL', 'http://localhost:8228/v1'), + os.environ.get('ANCHORE_CLI_SSL_VERIFY', True) + ) + action = actions.CreateFalcoRuleFromAnchoreStopPolicyResults(anchore_client) + + result = action.run() + + print(result) + + +if __name__ == '__main__': + main() diff --git a/integrations/anchore-falco/specs/create_falco_rule_from_anchore_policy_results_spec.py b/integrations/anchore-falco/specs/create_falco_rule_from_anchore_policy_results_spec.py new file mode 100644 index 00000000..409dce3d --- /dev/null +++ b/integrations/anchore-falco/specs/create_falco_rule_from_anchore_policy_results_spec.py @@ -0,0 +1,21 @@ +from mamba import description, it, before +from expects import expect, contain + +from doublex import Stub, when + +import actions +import infrastructure + + +with description(actions.CreateFalcoRuleFromAnchoreStopPolicyResults) as self: + with before.each: + self.anchore_client = Stub(infrastructure.AnchoreClient) + self.action = actions.CreateFalcoRuleFromAnchoreStopPolicyResults(self.anchore_client) + + with it('queries Anchore Server for images with Stop as policy results'): + image_id = 'any image id' + when(self.anchore_client).get_images_with_policy_result('stop').returns([image_id]) + + result = self.action.run() + + expect(result).to(contain(image_id)) diff --git a/integrations/anchore-falco/specs/infrastructure/anchore_client_spec.py b/integrations/anchore-falco/specs/infrastructure/anchore_client_spec.py new file mode 100644 index 00000000..021cdc24 --- /dev/null +++ b/integrations/anchore-falco/specs/infrastructure/anchore_client_spec.py @@ -0,0 +1,19 @@ +from mamba import description, it +from expects import expect, have_length, be_above + +import os + +import infrastructure + + +with description(infrastructure.AnchoreClient) as self: + with it('retrieves images with stop policy results'): + user = os.environ['ANCHORE_CLI_USER'] + password = os.environ['ANCHORE_CLI_PASS'] + url = os.environ['ANCHORE_CLI_URL'] + + client = infrastructure.AnchoreClient(user, password, url, True) + + result = client.get_images_with_policy_result('stop') + + expect(result).to(have_length(be_above(1)))