diff --git a/Pipfile b/Pipfile index 421dfed..f921375 100644 --- a/Pipfile +++ b/Pipfile @@ -12,6 +12,7 @@ httpx = "*" click = "*" aiosqlite = "*" sqlalchemy = {extras = ["asyncio"], version = "*"} +pyopenssl = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index 09e1403..a5d2e8f 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "01a59c7304004f92b993a672a37e535ee3b3816cdb77093d5431db2124afb567" + "sha256": "e6eaf14f53ea7b88c8245712c5639fa870ba5c7418f3f12697422d510386e6fc" }, "pipfile-spec": 6, "requires": { @@ -49,6 +49,64 @@ "markers": "python_version >= '3.6'", "version": "==2023.7.22" }, + "cffi": { + "hashes": [ + "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc", + "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a", + "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417", + "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab", + "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520", + "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36", + "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743", + "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8", + "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed", + "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684", + "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56", + "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324", + "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d", + "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235", + "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e", + "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088", + "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000", + "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7", + "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e", + "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673", + "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c", + "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe", + "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2", + "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098", + "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8", + "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a", + "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0", + "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b", + "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896", + "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e", + "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9", + "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2", + "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b", + "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6", + "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404", + "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f", + "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0", + "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4", + "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc", + "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936", + "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba", + "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872", + "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb", + "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614", + "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1", + "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d", + "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969", + "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b", + "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4", + "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627", + "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956", + "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357" + ], + "markers": "python_version >= '3.8'", + "version": "==1.16.0" + }, "click": { "hashes": [ "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", @@ -58,6 +116,35 @@ "markers": "python_version >= '3.7'", "version": "==8.1.7" }, + "cryptography": { + "hashes": [ + "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67", + "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311", + "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8", + "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13", + "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143", + "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f", + "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829", + "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd", + "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397", + "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac", + "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d", + "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a", + "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839", + "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e", + "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6", + "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9", + "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860", + "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca", + "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91", + "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d", + "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714", + "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb", + "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f" + ], + "markers": "python_version >= '3.7'", + "version": "==41.0.4" + }, "fastapi": { "hashes": [ "sha256:3270de872f0fe9ec809d4bd3d4d890c6d5cc7b9611d721d6438f9dacc8c4ef2e", @@ -74,6 +161,7 @@ "sha256:0d3f83ffb18dc57243e0151331e3c383b05e5b6c5029ac29f754745c800f8ed9", "sha256:10b5582744abd9858947d163843d323d0b67be9432db50f8bf83031032bc218d", "sha256:123910c58234a8d40eaab595bc56a5ae49bdd90122dde5bdc012c20595a94c14", + "sha256:1482fba7fbed96ea7842b5a7fc11d61727e8be75a077e603e8ab49d24e234383", "sha256:19834e3f91f485442adc1ee440171ec5d9a4840a1f7bd5ed97833544719ce10b", "sha256:1d363666acc21d2c204dd8705c0e0457d7b2ee7a76cb16ffc099d6799744ac99", "sha256:211ef8d174601b80e01436f4e6905aca341b15a566f35a10dd8d1e93f5dbb3b7", @@ -88,7 +176,6 @@ "sha256:4a1a6244ff96343e9994e37e5b4839f09a0207d35ef6134dce5c20d260d0302c", "sha256:4cd83fb8d8e17633ad534d9ac93719ef8937568d730ef07ac3a98cb520fd93e4", "sha256:527cd90ba3d8d7ae7dceb06fda619895768a46a1b4e423bdb24c1969823b8362", - "sha256:553d6fb2324e7f4f0899e5ad2c427a4579ed4873f42124beba763f16032959af", "sha256:56867a3b3cf26dc8a0beecdb4459c59f4c47cdd5424618c08515f682e1d46692", "sha256:621fcb346141ae08cb95424ebfc5b014361621b8132c48e538e34c3c93ac7365", "sha256:63acdc34c9cde42a6534518e32ce55c30f932b473c62c235a466469a710bfbf9", @@ -119,7 +206,6 @@ "sha256:bdd696947cd695924aecb3870660b7545a19851f93b9d327ef8236bfc49be705", "sha256:bdfaeecf8cc705d35d8e6de324bf58427d7eafb55f67050d8f28053a3d57118c", "sha256:be557119bf467d37a8099d91fbf11b2de5eb1fd5fc5b91598407574848dc910f", - "sha256:c3692ecf3fe754c8c0f2c95ff19626584459eab110eaab66413b1e7425cd84e9", "sha256:c6b5ce7f40f0e2f8b88c28e6691ca6806814157ff05e794cdd161be928550f4c", "sha256:c94e4e924d09b5a3e37b853fe5924a95eac058cb6f6fb437ebb588b7eda79870", "sha256:cc3e2679ea13b4de79bdc44b25a0c4fcd5e94e21b8f290791744ac42d34a0353", @@ -168,6 +254,13 @@ "markers": "python_version >= '3.5'", "version": "==3.4" }, + "pycparser": { + "hashes": [ + "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", + "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" + ], + "version": "==2.21" + }, "pydantic": { "hashes": [ "sha256:94f336138093a5d7f426aac732dcfe7ab4eb4da243c88f891d65deb4a2556ee7", @@ -288,6 +381,15 @@ "markers": "python_version >= '3.7'", "version": "==2.10.1" }, + "pyopenssl": { + "hashes": [ + "sha256:24f0dc5227396b3e831f4c7f602b950a5e9833d292c8e4a2e06b709292806ae2", + "sha256:276f931f55a452e7dea69c7173e984eb2a4407ce413c918aa34b55f82f9b8bac" + ], + "index": "pypi", + "markers": "python_version >= '3.6'", + "version": "==23.2.0" + }, "pyyaml": { "hashes": [ "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", diff --git a/README.md b/README.md index b4cfd3c..ac9abc5 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Features : Implemented checks : - [x] Returned status code matches what you expect ; -- [ ] Returned body matches what you expect ; +- [x] Returned body matches what you expect ; - [ ] SSL certificate expires in more than X days ; ## How to run ? diff --git a/argos/checks/base.py b/argos/checks/base.py index bc93f75..3f42dd1 100644 --- a/argos/checks/base.py +++ b/argos/checks/base.py @@ -6,7 +6,8 @@ import httpx from argos.schemas import Task - +# XXX We could name this Result, but is it could overlap with schemas.Result. +# Need to better define the naming around this. @dataclass class Response: status: str diff --git a/argos/checks/checks.py b/argos/checks/checks.py index c57aa3f..504479a 100644 --- a/argos/checks/checks.py +++ b/argos/checks/checks.py @@ -1,5 +1,10 @@ from argos.logging import logger from argos.checks.base import BaseCheck, Response, ExpectedIntValue, ExpectedStringValue +import ssl +import time + +from datetime import datetime +from OpenSSL import crypto class HTTPStatus(BaseCheck): @@ -14,7 +19,7 @@ class HTTPStatus(BaseCheck): return self.response( response.status_code == self.expected, expected=self.expected, - retrieved=response.status_code + retrieved=response.status_code, ) @@ -24,9 +29,7 @@ class HTTPBodyContains(BaseCheck): async def run(self) -> dict: response = await self.client.request(method="get", url=self.task.url) - return self.response( - self.expected in response.text - ) + return self.response(self.expected in response.text) class SSLCertificateExpiration(BaseCheck): @@ -34,4 +37,23 @@ class SSLCertificateExpiration(BaseCheck): expected_cls = ExpectedStringValue async def run(self): - return True + response = await self.client.get(self.task.url) + if response.is_error: + raise + + conn = self.client.transport.get_connection_info(self.task.url) + cert = ssl.DER_cert_to_PEM_cert(conn.raw_certificates[0]) + + x509 = crypto.load_certificate(crypto.FILETYPE_PEM, cert) + not_after = x509.get_notAfter().decode("utf-8") + not_after = datetime.strptime(not_after, "%Y%m%d%H%M%SZ") + + now = time.time() + if time.mktime(not_after.timetuple()) < now: + expired = True + else: + expired = False + + return self.response( + expired == False, expected=now, retrieved=not_after.timetuple() + ) diff --git a/argos/schemas/config.py b/argos/schemas/config.py index c38aa36..359cbd6 100644 --- a/argos/schemas/config.py +++ b/argos/schemas/config.py @@ -3,6 +3,8 @@ from pydantic import BaseModel from enum import StrEnum from typing import List, Optional, Tuple +from typing import Dict, Union, List + import yaml from pydantic import BaseModel, Field, HttpUrl, validator @@ -22,8 +24,27 @@ class SSL(BaseModel): thresholds: Thresholds -WebsiteCheck = dict[str, str | int] -# StrEnum("Check", get_check_names()) ? +class WebsiteCheck(BaseModel): + key: str + value: str | List[str] | Dict[str, str] + + class Config: + arbitrary_types_allowed = True + + @classmethod + def __get_validators__(cls): + yield cls.validate + + @classmethod + def validate(cls, value): + if isinstance(value, str): + return {"expected": value} + elif isinstance(value, dict): + return value + elif isinstance(value, list): + return {"expected": value} + else: + raise ValueError("Invalid type") class WebsitePath(BaseModel): diff --git a/config.yaml b/config.yaml index c6527d7..b487e1a 100644 --- a/config.yaml +++ b/config.yaml @@ -20,13 +20,70 @@ ssl: warning: "10d" websites: - - domain: "https://blog.notmyidea.org" + - domain: "https://mypads.framapad.org" + paths: + - path: "/mypads/" + checks: + - status-is: 200 + - body-contains: '
' + # le check du certificat devrait plutôt être au niveau + # de domain et paths, AMHA + - ssl-certificate-expiration: "on-check" + - path: "/admin/" + checks: + - status-is: 401 + - domain: "https://munin.framasoft.org" paths: - path: "/" checks: - - status-is: 200 - - body-contains: "Alexis" - - ssl-certificate-expiration: "on-check" - - path: "/foo" + - status-is: 301 + - path: "/munin/" checks: - - status-is: 400 + - status-is: 401 + - domain: "https://framagenda.org" + paths: + - path: "/status.php" + checks: + - status-is: 200 + # Là, idéalement, il faudrait un json-contains, + # qui serait une table de hachage + - body-contains: '"maintenance":false' + - ssl-certificate-expiration: "on-check" + - path: "/" + checks: + - status-is: 302 + - path: "/login" + checks: + - status-is: 200 + - domain: "https://framadrive.org" + paths: + - path: "/status.php" + checks: + - status-is: 200 + - body-contains: '"maintenance":false' + - ssl-certificate-expiration: "on-check" + - path: "/" + checks: + - status-is: 302 + - path: "/login" + checks: + - status-is: 200 + - domain: "https://cloud.framabook.org" + paths: + - path: "/status.php" + checks: + - status-is: 200 + - body-contains: '"maintenance":false' + - ssl-certificate-expiration: "on-check" + - path: "/" + checks: + - status-is: 302 + - path: "/login" + checks: + - status-is: 200 + - domain: "https://framasoft.org" + path: + - path: "/" + checks: + - status-is: 200 + - ssl-certificate-expiration: "on-check"