refact(exporter): delete whole repo in preparation for v2

BREAKING CHANGE : The project is being rewritten in go
This commit is contained in:
Arthur Chaloin
2020-10-12 13:53:22 +02:00
parent 3059c609e1
commit 90f1eb6a31
8 changed files with 0 additions and 394 deletions
-42
View File
@@ -1,42 +0,0 @@
stages:
- build
- release
- publish
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
build:
stage: build
image: docker:stable-dind
script:
- echo "Logging to GitLab Container Registry with CI credentials..."
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
- docker build --tag $IMAGE_TAG .
- docker push $IMAGE_TAG
semantic-release:
stage: release
image: enix/semantic-release:gitlab
only:
- master
except:
- tags
script:
- semantic-release --ci
publish-docker-hub:
stage: publish
only:
refs:
- /^v([0-9]|[1-9][0-9]*)\.([0-9]|[1-9][0-9]*)\.([0-9]|[1-9][0-9]*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/
image: docker:stable
script:
- echo "Logging to GitLab Container Registry with CI credentials..."
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
- docker pull $IMAGE_TAG
- docker tag $IMAGE_TAG $DOCKER_HUB_REPOSITORY/$CI_PROJECT_NAME:${CI_COMMIT_REF_NAME:1}
- echo "Logging to Docker Hub Registry with Enix credentials..."
- docker login -u "$DOCKER_HUB_USER" -p "$DOCKER_HUB_PASSWORD"
- echo "Push release $CI_COMMIT_REF_NAME ..."
- docker push $DOCKER_HUB_REPOSITORY/$CI_PROJECT_NAME:${CI_COMMIT_REF_NAME:1}
-9
View File
@@ -1,9 +0,0 @@
{
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
["@semantic-release/gitlab", {
"gitlabUrl": "https://gitlab.enix.io"
}]
]
}
-11
View File
@@ -1,11 +0,0 @@
FROM python:3.7
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY x509-exporter .
EXPOSE 9090
ENTRYPOINT [ "python", "./x509-exporter" ]
CMD ["--port=9090"]
-20
View File
@@ -1,20 +0,0 @@
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFNE1USXlOakV6TWpjek5Gb1hEVEk0TVRJeU16RXpNamN6TkZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBUEtYCkZKM0pRUDFlS1UvazMvV2sweWFLSEQvT2Y1cVdrMlAzMVV1aW9tNko5UlM2TWxOeFdERkJlcnF6UWlzQ0g2aisKYStzM3hpM2dmUXVlVnF6VCtQelRpNWgzSStHcFRnajlmdTh6WXkxbURiK3RveXRqSHVzOUU2RVF4ZnF0SGRUeAoza2RESlplVWJCZUdNSXlSR3FMeXNPT1BWdXB3UWI3MDFlZ1FyWHdtbExvcjJmaUxQR0FieUdBQjByY0ViQ1pECmh2MzA1bTZTRXY3WU9KTjRvVkwxTGpZRk5FdlpCVXFJTFIvS25DeGNKc3BXa2pQSU9tQi9VWElMbExjaElEaGwKQ2FaZWlvZWI3WlByY2Jaem5qYkhPY2lZaUlTSTUwSis2MXl3eHoxZzBKUDJpZnRiZmMwTC9QWVQ2SDl4MS94cQo0K1c1QUpXVmVWeVFJZWhmcDZjQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFMTDhCNWxqTkhycGo0a0kxL0ZBSmZqaDhBa3UKTzdpMkhLR2RHZ0ZybUdMeUdaRTNSRERDa3I2UFpRdlVYQkhmTzM2dDc3S21wYW1FOTUrYTJkOWt6Yld3TUl3NQpSMERlKy95eHJDa3BRcTVma2lJQW9VMEIra1lGdmdmb1llMm9RUjBVSXN1SWNJcVNvQmtnUjlGQXYzdDBHV25iClZZa1VKOTlxQm5HT09OU1RLc09Ib29RemkrVnNJN0M3S1BvZTMzNkJaVnlWTTRHS1dJSnlNSTdPKysvUnFVK2YKa0FxQnVzcHBuZGxXREZsalZwNk5ydkhiZjRaaWxDbEluUVN0NHg3N0h6V0FSamo3Y3ZNb01nSEpEVXZJQS9NRwpIZEFYYjJ2N3VpZjhLTzFKK0Zza0tQdmVDdWNCeDhIUDlGTlIxNnZSSDVkZFBPK0xEU0xCMVNZZ3UrYz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
server: https://127.0.0.1:6443
name: default-cluster
contexts:
- context:
cluster: default-cluster
namespace: default
user: default-auth
name: default-context
current-context: default-context
kind: Config
preferences: {}
users:
- name: default-auth
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFNE1USXlOakV6TWpjek5Gb1hEVEk0TVRJeU16RXpNamN6TkZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBUEtYCkZKM0pRUDFlS1UvazMvV2sweWFLSEQvT2Y1cVdrMlAzMVV1aW9tNko5UlM2TWxOeFdERkJlcnF6UWlzQ0g2aisKYStzM3hpM2dmUXVlVnF6VCtQelRpNWgzSStHcFRnajlmdTh6WXkxbURiK3RveXRqSHVzOUU2RVF4ZnF0SGRUeAoza2RESlplVWJCZUdNSXlSR3FMeXNPT1BWdXB3UWI3MDFlZ1FyWHdtbExvcjJmaUxQR0FieUdBQjByY0ViQ1pECmh2MzA1bTZTRXY3WU9KTjRvVkwxTGpZRk5FdlpCVXFJTFIvS25DeGNKc3BXa2pQSU9tQi9VWElMbExjaElEaGwKQ2FaZWlvZWI3WlByY2Jaem5qYkhPY2lZaUlTSTUwSis2MXl3eHoxZzBKUDJpZnRiZmMwTC9QWVQ2SDl4MS94cQo0K1c1QUpXVmVWeVFJZWhmcDZjQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFMTDhCNWxqTkhycGo0a0kxL0ZBSmZqaDhBa3UKTzdpMkhLR2RHZ0ZybUdMeUdaRTNSRERDa3I2UFpRdlVYQkhmTzM2dDc3S21wYW1FOTUrYTJkOWt6Yld3TUl3NQpSMERlKy95eHJDa3BRcTVma2lJQW9VMEIra1lGdmdmb1llMm9RUjBVSXN1SWNJcVNvQmtnUjlGQXYzdDBHV25iClZZa1VKOTlxQm5HT09OU1RLc09Ib29RemkrVnNJN0M3S1BvZTMzNkJaVnlWTTRHS1dJSnlNSTdPKysvUnFVK2YKa0FxQnVzcHBuZGxXREZsalZwNk5ydkhiZjRaaWxDbEluUVN0NHg3N0h6V0FSamo3Y3ZNb01nSEpEVXZJQS9NRwpIZEFYYjJ2N3VpZjhLTzFKK0Zza0tQdmVDdWNCeDhIUDlGTlIxNnZSSDVkZFBPK0xEU0xCMVNZZ3UrYz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
client-key-data: bG9sCg==
-20
View File
@@ -1,20 +0,0 @@
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFNE1USXlOakV6TWpjek5Gb1hEVEk0TVRJeU16RXpNamN6TkZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBUEtYCkZKM0pRUDFlS1UvazMvV2sweWFLSEQvT2Y1cVdrMlAzMVV1aW9tNko5UlM2TWxOeFdERkJlcnF6UWlzQ0g2aisKYStzM3hpM2dmUXVlVnF6VCtQelRpNWgzSStHcFRnajlmdTh6WXkxbURiK3RveXRqSHVzOUU2RVF4ZnF0SGRUeAoza2RESlplVWJCZUdNSXlSR3FMeXNPT1BWdXB3UWI3MDFlZ1FyWHdtbExvcjJmaUxQR0FieUdBQjByY0ViQ1pECmh2MzA1bTZTRXY3WU9KTjRvVkwxTGpZRk5FdlpCVXFJTFIvS25DeGNKc3BXa2pQSU9tQi9VWElMbExjaElEaGwKQ2FaZWlvZWI3WlByY2Jaem5qYkhPY2lZaUlTSTUwSis2MXl3eHoxZzBKUDJpZnRiZmMwTC9QWVQ2SDl4MS94cQo0K1c1QUpXVmVWeVFJZWhmcDZjQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFMTDhCNWxqTkhycGo0a0kxL0ZBSmZqaDhBa3UKTzdpMkhLR2RHZ0ZybUdMeUdaRTNSRERDa3I2UFpRdlVYQkhmTzM2dDc3S21wYW1FOTUrYTJkOWt6Yld3TUl3NQpSMERlKy95eHJDa3BRcTVma2lJQW9VMEIra1lGdmdmb1llMm9RUjBVSXN1SWNJcVNvQmtnUjlGQXYzdDBHV25iClZZa1VKOTlxQm5HT09OU1RLc09Ib29RemkrVnNJN0M3S1BvZTMzNkJaVnlWTTRHS1dJSnlNSTdPKysvUnFVK2YKa0FxQnVzcHBuZGxXREZsalZwNk5ydkhiZjRaaWxDbEluUVN0NHg3N0h6V0FSamo3Y3ZNb01nSEpEVXZJQS9NRwpIZEFYYjJ2N3VpZjhLTzFKK0Zza0tQdmVDdWNCeDhIUDlGTlIxNnZSSDVkZFBPK0xEU0xCMVNZZ3UrYz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
server: https://127.0.0.1:6443
name: default-cluster
contexts:
- context:
cluster: default-cluster
namespace: default
user: default-auth
name: default-context
current-context: default-context
kind: Config
preferences: {}
users:
- name: default-auth
user:
client-certificate: ./one-cert.pem
client-key: ./does-not-exist.pem
-17
View File
@@ -1,17 +0,0 @@
-----BEGIN CERTIFICATE-----
MIICyDCCAbCgAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
cm5ldGVzMB4XDTE4MTIyNjEzMjczNFoXDTI4MTIyMzEzMjczNFowFTETMBEGA1UE
AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPKX
FJ3JQP1eKU/k3/Wk0yaKHD/Of5qWk2P31Uuiom6J9RS6MlNxWDFBerqzQisCH6j+
a+s3xi3gfQueVqzT+PzTi5h3I+GpTgj9fu8zYy1mDb+toytjHus9E6EQxfqtHdTx
3kdDJZeUbBeGMIyRGqLysOOPVupwQb701egQrXwmlLor2fiLPGAbyGAB0rcEbCZD
hv305m6SEv7YOJN4oVL1LjYFNEvZBUqILR/KnCxcJspWkjPIOmB/UXILlLchIDhl
CaZeioeb7ZPrcbZznjbHOciYiISI50J+61ywxz1g0JP2iftbfc0L/PYT6H9x1/xq
4+W5AJWVeVyQIehfp6cCAwEAAaMjMCEwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB
/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBALL8B5ljNHrpj4kI1/FAJfjh8Aku
O7i2HKGdGgFrmGLyGZE3RDDCkr6PZQvUXBHfO36t77KmpamE95+a2d9kzbWwMIw5
R0De+/yxrCkpQq5fkiIAoU0B+kYFvgfoYe2oQR0UIsuIcIqSoBkgR9FAv3t0GWnb
VYkUJ99qBnGOONSTKsOHooQzi+VsI7C7KPoe336BZVyVM4GKWIJyMI7O++/RqU+f
kAqBusppndlWDFljVp6NrvHbf4ZilClInQSt4x77HzWARjj7cvMoMgHJDUvIA/MG
HdAXb2v7uif8KO1J+FskKPveCucBx8HP9FNR16vRH5ddPO+LDSLB1SYgu+c=
-----END CERTIFICATE-----
-4
View File
@@ -1,4 +0,0 @@
pyOpenSSL
prometheus_client
pyasn1
PyYAML
-271
View File
@@ -1,271 +0,0 @@
#!/usr/bin/env python3
import argparse
import logging
import os
import time
import base64
import yaml
from OpenSSL import crypto
from prometheus_client import start_http_server
from prometheus_client.core import GaugeMetricFamily, REGISTRY
from pyasn1.type.useful import GeneralizedTime
def parse_args():
"""Return the parsed arguments."""
parser = argparse.ArgumentParser()
parser.add_argument(
'-f',
'--watch-file',
action='append',
metavar='FILE_PATH',
help='one or more x509 certificate file')
parser.add_argument(
'-d',
'--watch-dir',
action='append',
metavar='DIR_PATH',
help='one or more directory which contains x509 certificate files')
parser.add_argument(
'-k',
'--watch-kubeconf',
action='append',
metavar='KUBE_PATH',
help='one or more Kubernetes client configuration (kind Config) which'
' contains embedded x509 certificates or PEM file paths')
parser.add_argument(
'-p',
'--port',
default=9090,
metavar='PORT',
type=int,
help='prometheus exporter listening port')
parser.add_argument(
'--debug',
action='store_true',
help='enable debug mode')
return parser.parse_args()
def yaml_parser(fileobj):
"""Returns a PyYAML parser avoiding warnings."""
try:
parser = yaml.load(fileobj, Loader=yaml.FullLoader)
except AttributeError:
parser = yaml.load(fileobj)
return parser
def parse_x509(filepath=None, b64=None, plain=None):
"""x509 parsing helper."""
if filepath:
plain = open(filepath, 'rt').read()
elif b64:
plain = base64.b64decode(b64)
return crypto.load_certificate(crypto.FILETYPE_PEM, plain)
def walk_x509_files_and_dirs(args):
"""Compile a deduped list of files."""
x509_filepaths = set([])
if args.watch_file:
for filename in args.watch_file:
try:
os.stat(filename)
x509_filepaths.add(filename)
except:
logging.warning("not able to stat file: %s", filename)
if args.watch_dir:
for directory in args.watch_dir:
for filename in os.listdir(directory):
filepath = os.path.join(directory, filename)
if os.path.isfile(filepath):
x509_filepaths.add(filepath)
return x509_filepaths
def walk_kubeconf_files(args):
"""Compile a deduped list of Kubernetes client configurations."""
kubeconf_filepaths = set([])
if args.watch_kubeconf:
for filename in args.watch_kubeconf:
try:
with open(filename, 'r') as kubeconf:
conf = yaml_parser(kubeconf)
if conf['kind'] == 'Config' and conf['apiVersion'] == 'v1':
kubeconf_filepaths.add(filename)
except:
logging.warning("not able to parse file: %s", filename)
return kubeconf_filepaths
def check_certificates(x509_filepaths):
"""Verify file content and sanitize the filepaths."""
valid_certificates = []
for x509_filepath in x509_filepaths:
try:
parse_x509(filepath=x509_filepath)
logging.info("valid x509 certificate detected: %s",
x509_filepath)
valid_certificates += [x509_filepath]
except:
logging.warning("not able to parse: %s", x509_filepath)
return valid_certificates
class X509Collector:
"""a prometheus x509 collector."""
def __init__(self, x509_filepaths, kubeconf_filepaths):
"""Take a set of filepaths as argument."""
self.x509_filepaths = x509_filepaths
self.kubeconf_filepaths = kubeconf_filepaths
def serialize_x509name(self, x509name):
"""Helper to serialize a x509Name as a string."""
return b' '.join(
b'='.join(component) for component in x509name.get_components()
).decode('utf-8')
def add_x509name_components_labels(self, x509name, labels, prefix):
"""Add x509 name components to label list."""
if x509name.C:
labels[prefix + '_C'] = x509name.C
if x509name.ST:
labels[prefix + '_ST'] = x509name.ST
if x509name.L:
labels[prefix + '_L'] = x509name.L
if x509name.O:
labels[prefix + '_O'] = x509name.O
if x509name.OU:
labels[prefix + '_OU'] = x509name.OU
if x509name.CN:
labels[prefix + '_CN'] = x509name.CN
def build_labels(self, x509, filepath, embedded=None):
"""Return prometheus metric labels."""
labels = {
'filename': os.path.basename(filepath),
'filepath': filepath,
'serial_number': "%s" % x509.get_serial_number(),
}
if embedded:
labels.update(dict(zip(('embedded_kind', 'embedded_key'),
embedded)))
self.add_x509name_components_labels(
x509.get_issuer(),
labels, "issuer")
self.add_x509name_components_labels(
x509.get_subject(),
labels, "subject")
return labels
def build_metrics(self, x509):
"""Return prometheus metrics."""
x509_not_before_asn1_time = GeneralizedTime(x509.get_notBefore())
x509_not_before_ts = x509_not_before_asn1_time.asDateTime.timestamp()
x509_not_after_asn1_time = GeneralizedTime(x509.get_notAfter())
x509_not_after_ts = x509_not_after_asn1_time.asDateTime.timestamp()
expired = float(x509.has_expired())
return (x509_not_before_ts, x509_not_after_ts, expired)
def collect(self):
"""The main Prometheus collector function."""
kubex509_filepaths = []
for kubeconf_filepath in self.kubeconf_filepaths:
try:
with open(kubeconf_filepath, 'r') as kubeconf:
conf = yaml_parser(kubeconf)
cert_roots = [('cluster', 'certificate-authority'),
('user', 'client-certificate')]
for item_type, key_base in cert_roots:
for item in conf.get("{}s".format(item_type), []):
name = item.get("name")
item_data = item.get(item_type)
if not name or not item_data:
continue
if key_base in item_data:
kubex509_filepaths.append(item_data.get(key_base))
mbed_key = "{}-data".format(key_base)
if mbed_key in item_data:
x509 = parse_x509(b64=item_data.get(mbed_key))
labels = self.build_labels(x509, kubeconf_filepath,
embedded=(item_type, name))
yield from self.emit_x509(x509, labels)
except:
logging.warning("not able to parse and compute: %s",
kubeconf_filepath)
for x509_filepath in self.x509_filepaths + kubex509_filepaths:
try:
x509 = parse_x509(filepath=x509_filepath)
yield from self.emit_x509(x509,
self.build_labels(x509, x509_filepath))
except:
logging.warning("not able to parse and compute: %s",
x509_filepath)
def emit_x509(self, x509, labels):
"""Produce Prometheus metrics."""
not_before, not_after, expired = self.build_metrics(x509)
metric = GaugeMetricFamily(
'x509_cert_not_before',
"x509 certificate not_before timestamp",
labels=labels.keys())
metric.add_metric(labels.values(), not_before)
yield metric
metric = GaugeMetricFamily(
'x509_cert_not_after',
"x509 certificate not_after timestamp",
labels=labels.keys())
metric.add_metric(labels.values(), not_after)
yield metric
metric = GaugeMetricFamily(
'x509_cert_expired',
"x509 certificate expiration boolean",
labels=labels.keys())
metric.add_metric(labels.values(), expired)
yield metric
LOOP_SLEEP_TIME = 300
def main():
"""x509-exporter main function."""
args = parse_args()
logging.basicConfig(
level=logging.DEBUG if args.debug else logging.INFO,
format='%(asctime)s %(levelname)s %(message)s')
x509_filepaths = walk_x509_files_and_dirs(args)
x509_filepaths = check_certificates(x509_filepaths)
kubeconfig_filepaths = walk_kubeconf_files(args)
logging.info("monitoring %s certificate(s) and %s kubeconfig(s)",
len(x509_filepaths), len(kubeconfig_filepaths))
REGISTRY.register(X509Collector(x509_filepaths, kubeconfig_filepaths))
logging.info("starting exporter on port %d", args.port)
start_http_server(args.port)
while True:
time.sleep(LOOP_SLEEP_TIME)
if __name__ == '__main__':
main()