From cebe108bd3854a1d4482a2aad1716372da4a8cce Mon Sep 17 00:00:00 2001 From: Nathan Pierce Date: Tue, 17 Sep 2024 20:39:33 -0500 Subject: [PATCH] v4.0.0 --- .gitignore | 13 ++++- README.md | 73 +++++++++++++++++++--------- VERSION | 2 +- docker/build/Dockerfile | 6 ++- docker/build/docker-compose.yml | 29 +++++------ docker/scratch/docker-compose.yml | 11 +++-- go.mod | 33 +++++++++---- go.sum | 80 ++++++++++++++++++++++--------- main.go | 64 +++++++++++++++---------- src/client/client.go | 8 ++-- src/client/communicator.go | 6 +-- src/client/tls.go | 12 ++--- src/log/log.go | 72 +++++++++++++--------------- src/server/server.go | 48 +++++++++++++++++-- 14 files changed, 295 insertions(+), 162 deletions(-) diff --git a/.gitignore b/.gitignore index 6c1bd30..6a2059b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,15 @@ bin/* dist/ anka-prometheus-exporter_linux* anka-prometheus-exporter_darwin* -docker-compose.yml \ No newline at end of file +docker-compose.yml +*.pem +*.crt +*.key +*.csr +*.p12 +*.der +*.cer +*.pfx +*.p12 +*.p12 +web.config.yml diff --git a/README.md b/README.md index cb7866c..d96dfb2 100644 --- a/README.md +++ b/README.md @@ -18,45 +18,48 @@ Ensure you have a functioning Prometheus instance before using this. | ANKA_PROMETHEUS_EXPORTER_INTERVAL (int) | --interval (int) | | ANKA_PROMETHEUS_EXPORTER_PORT (int) | --port (int) | | ANKA_PROMETHEUS_EXPORTER_DISABLE_INTERVAL_OPTIMIZER (bool) | --disable-interval-optimizer | -| ANKA_PROMETHEUS_EXPORTER_TLS (bool) | --tls | -| ANKA_PROMETHEUS_EXPORTER_SKIP_TLS_VERIFICATION (bool) | --skip-tls-verification | -| ANKA_PROMETHEUS_EXPORTER_CA_CERT (string) | --ca-cert (string) | +| ANKA_PROMETHEUS_EXPORTER_CLIENT_TLS (bool) | --client-tls | +| ANKA_PROMETHEUS_EXPORTER_CLIENT_SKIP_TLS_VERIFICATION (bool) | --client-skip-tls-verification | +| ANKA_PROMETHEUS_EXPORTER_CLIENT_CA_CERT (string) | --client-ca-cert (string) | | ANKA_PROMETHEUS_EXPORTER_CLIENT_CERT (string) | --client-cert (string) | | ANKA_PROMETHEUS_EXPORTER_CLIENT_CERT_KEY (string) | --client-cert-key (string) | | ANKA_PROMETHEUS_EXPORTER_UAK_ID (string) | --uak-id (string) | | ANKA_PROMETHEUS_EXPORTER_UAK_PATH (string) | --uak-path (string) | | ANKA_PROMETHEUS_EXPORTER_UAK_STRING (string) | --uak-string (string) | +| ANKA_PROMETHEUS_EXPORTER_WEB_CONFIG_FILE (string) | --web.config.file (string) | ```bash Usage of anka-prometheus-exporter: - -ca-cert string - Path to ca PEM/x509 file (cert file path as arg) + -client-ca-cert string + Path to client CA PEM/x509 file (cert file path as arg) -client-cert string Path to client cert PEM/x509 file (cert file path as arg) -client-cert-key string Path to client key PEM/x509 file (cert file path as arg) + -client-skip-tls-verification + Skip client TLS verification (no args) + -client-tls + Enable client TLS (no args) -controller-address string Controller address to monitor (url as arg) (required) - -controller-username string - Controller username with basic root token (username as arg) -controller-password string - Controller password with basic root token (password as arg) + Controller basic auth password (password as arg) + -controller-username string + Controller basic auth username (username as arg) -disable-interval-optimizer - Optimize interval according to /metric api requests receieved (no args) + Optimize interval according to /metric api requests received (no args) -interval int Seconds to wait between data requests to controller (int as arg) (default 15) - -port int - Port to server /metrics endpoint (int as arg) (default 2112) - -skip-tls-verification - Skip TLS verification (no args) - -tls - Enable TLS (no args) -uak-id string UAK ID you wish to use for Controller requests (string as arg) + -uak-path string + Path to the UAK file used for Controller requests (path as arg) (supersedes -uak-string) -uak-string string String form (cat myUAK.pem | sed '1,1d' | sed '$d' | tr -d '\n') of the key file contents for Controller requests (string as arg) - -uak-path string - Path to the UAK for Controller requests (path as arg) (takes priority over -uak-string if both are specified) + -web.config.file string + Path to configuration file that can enable server TLS or authentication. See: https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md + -web.listen-address :2112 + Address on which to expose metrics and web interface. Examples: :2112 or `[::1]:2112` for http, `vsock://:2112` for vsock ``` > `LOG_LEVEL` can be set using an environment variable @@ -101,21 +104,38 @@ scrape_configs: ## Using TLS -The `--tls` flag is not required if your controller certificate is signed with a major CA and no client authentication is configured. For all other TLS configuration options, like self signed scenarios, `--tls` must be enabled. +Protecting your metrics endpoint with TLS is possible using the `web.config.file` flag. It looks something like this: + +`-web.config.file web.config.yml` or `ANKA_PROMETHEUS_EXPORTER_WEB_CONFIG_FILE=web.config.yml` + +```yaml +tls_server_config: + cert_file: server.crt + key_file: server.key + +# Usernames and passwords required to connect. +# Passwords are hashed with bcrypt: https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md#about-bcrypt. +# basic_auth_users: +# test: $2y$10$Cfzh3pBDQmxAuWyg87p.r.sRJvPkkdhf8iyF.WgXbHWi8BsTbhHWi +``` + +This allows you to protect your metrics endpoint with TLS/https. You'd then load your metrics endpoint with `https://{URL}:2112/metrics`. -For self signed certificates, you can either use `--skip-tls-verification` or provide your ca-cert with `--ca-cert`. +## Using Auth -### Using Auth +You have two options for authentication with the Controller. -- If client authentication is set on the controller, use `--client-cert` and `--client-cert-key`. -- If RTA is enabled, you can use basic auth through: +1. If [mtls](https://docs.veertu.com/anka/anka-build-cloud/advanced-security-features/certificate-authentication/) is enabled on the controller, use `-client-cert` and `-client-cert-key`. +2. If RTA is enabled, you can use basic auth through: ```bash -controller-username string Controller username with basic root token (username as arg) -controller-password string - Controller password with basic root token (password as arg) + Controller password with basic root token (password as arg) ``` +The `-client-tls` flag is not required if your controller certificate is signed with a major CA and no client authentication is configured. For all other TLS configuration options, like self signed scenarios, `--tls` must be enabled. For self signed certificates, you can either use `-client-skip-tls-verification` or provide your ca-cert with `-client-ca-cert`. + --- ## Exposed Metrics @@ -182,6 +202,7 @@ anka_registry_template_tags_count | Count of Tags in the Registry for the Templa | current version | target version | notes | | --------------- | -------------- | ----- | +| v3.x | v4.x | Client certs for mTLS were changed. You will need to update your config to the new `--client-*` flags. | | v2.x | v3.x | The metrics `anka_nodes_instance_capacity` and `anka_node_states_count` are now split by architecture (label: `arch`). Almost all `anka_node_*` metrics now also include `arch` as a label. You must also be running the Anka Build Cloud Controller >= v1.22.0 as architecture was added in this version. | --- @@ -191,3 +212,9 @@ anka_registry_template_tags_count | Count of Tags in the Registry for the Templa ```bash make build-and-run ARGUMENTS="--controller-username root --controller-password 1111111111" ``` + +or + +```bash +go run -ldflags="-X main.version=$(cat VERSION) -X main.commit=$(git rev-parse HEAD)" main.go --controller-address http://anka.controller:8090 +``` diff --git a/VERSION b/VERSION index 13d683c..0c89fc9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0.1 \ No newline at end of file +4.0.0 \ No newline at end of file diff --git a/docker/build/Dockerfile b/docker/build/Dockerfile index bb92cce..74322e7 100644 --- a/docker/build/Dockerfile +++ b/docker/build/Dockerfile @@ -6,5 +6,7 @@ RUN make build-linux ###################### FROM alpine LABEL maintainer="Veertu Inc. support@veertu.com" -COPY --from=builder /build/bin/anka-prometheus-exporter_linux_amd64 /usr/bin/anka-prometheus-exporter -ENTRYPOINT ["/bin/sh", "-c", "/usr/bin/anka-prometheus-exporter --controller-address $CONTROLLER_ADDR $CONTROLLER_USERNAME $CONTROLLER_PASSWORD $DISABLE_INTERVAL_OPTIMIZER $ANKA_PROMETHEUS_PORT $ANKA_PROMETHEUS_INTERVAL $USE_TLS $SKIP_TLS $CA_CERT $CLIENT_CERT $CLIENT_KEY"] +ARG TARGETARCH +COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +COPY ./anka-prometheus-exporter_linux_${TARGETARCH} /anka-prometheus-exporter +ENTRYPOINT ["/anka-prometheus-exporter"] \ No newline at end of file diff --git a/docker/build/docker-compose.yml b/docker/build/docker-compose.yml index 91e1e2f..cc93cbf 100644 --- a/docker/build/docker-compose.yml +++ b/docker/build/docker-compose.yml @@ -6,21 +6,16 @@ services: - "2112:2112" # volumes: # - **HOST CERTS FOLDER**:/certs + volumes: + - ./web-config.yml:/config/web-config.yml environment: - CONTROLLER_ADDR: # Replace this with your CONTROLLER URL | Example: "http://host.docker.internal:8090" - - # Controller requests interval is optimized according to incoming /metrics request. To disable, uncomment the next line - # DISABLE_INTERVAL_OPTIMIZER: "--disable-optimize-interval" - - # Optional variables: - # ANKA_PROMETHEUS_PORT: "--port 2112" - # ANKA_PROMETHEUS_INTERVAL: "--interval ***ENTER NUM OF SECONDS HERE***" - - # TLS Options: - # USE_TLS: "--tls" - # SKIP_TLS: "--skip-tls-verification" - # CA_CERT: "--ca-cert /certs/**ENTER CA CERT FILE NAME HERE**" - # CLIENT_CERT: "--client-cert /certs/**ENTER CLIENT CERT FILE NAME HERE**" - # CLIENT_KEY: "--client-cert-key /certs/**ENTER CLIENT KEY FILE NAME HERE**" - # CONTROLLER_USERNAME: "--controller-username root" - # CONTROLLER_PASSWORD: "--controller-password {PASSWORD HERE}" + - ANKA_PROMETHEUS_EXPORTER_CONTROLLER_ADDRESS=https://host.docker.internal:8090 # change to be your url and port + #- ANKA_PROMETHEUS_EXPORTER_INTERVAL=10 + #- ANKA_PROMETHEUS_EXPORTER_PORT=2112 + #- ANKA_PROMETHEUS_EXPORTER_DISABLE_INTERVAL_OPTIMIZER=true + #- ANKA_PROMETHEUS_EXPORTER_TLS=true + #- ANKA_PROMETHEUS_EXPORTER_SKIP_TLS_VERIFICATION=true + #- ANKA_PROMETHEUS_EXPORTER_CA_CERT=/certs/ca.crt + #- ANKA_PROMETHEUS_EXPORTER_CLIENT_CERT=/certs/client.crt + #- ANKA_PROMETHEUS_EXPORTER_CLIENT_KEY=/certs/client.key + #- ANKA_PROMETHEUS_EXPORTER_WEB_CONFIG_FILE=/config/web-config.yml diff --git a/docker/scratch/docker-compose.yml b/docker/scratch/docker-compose.yml index 8d5f985..d2cc009 100644 --- a/docker/scratch/docker-compose.yml +++ b/docker/scratch/docker-compose.yml @@ -5,13 +5,16 @@ services: container_name: anka-prometheus-exporter ports: - "2112:2112" + #volumes: + # - web-config.yml:/config/web-config.yml environment: - ANKA_PROMETHEUS_EXPORTER_CONTROLLER_ADDRESS=https://host.docker.internal:8090 # change to be your url and port # - ANKA_PROMETHEUS_EXPORTER_INTERVAL # - ANKA_PROMETHEUS_EXPORTER_PORT # - ANKA_PROMETHEUS_EXPORTER_DISABLE_INTERVAL_OPTIMIZER - # - ANKA_PROMETHEUS_EXPORTER_TLS - # - ANKA_PROMETHEUS_EXPORTER_SKIP_TLS_VERIFICATION - # - ANKA_PROMETHEUS_EXPORTER_CA_CERT + # - ANKA_PROMETHEUS_EXPORTER_CLIENT_TLS + # - ANKA_PROMETHEUS_EXPORTER_CLIENT_SKIP_TLS_VERIFICATION + # - ANKA_PROMETHEUS_EXPORTER_CLIENT_CA_CERT # - ANKA_PROMETHEUS_EXPORTER_CLIENT_CERT - # - ANKA_PROMETHEUS_EXPORTER_CLIENT_CERT_KEY \ No newline at end of file + # - ANKA_PROMETHEUS_EXPORTER_CLIENT_CERT_KEY + # - ANKA_PROMETHEUS_EXPORTER_WEB_CONFIG_FILE=/config/web-config.yml \ No newline at end of file diff --git a/go.mod b/go.mod index 35c8b89..2243a9b 100644 --- a/go.mod +++ b/go.mod @@ -1,18 +1,33 @@ module github.com/veertuinc/anka-prometheus-exporter -go 1.21 +go 1.22 + +toolchain go1.22.5 require ( - github.com/prometheus/client_golang v1.19.1 - github.com/sirupsen/logrus v1.9.3 + github.com/prometheus/client_golang v1.20.4 + github.com/prometheus/exporter-toolkit v0.13.0 ) require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.48.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect - golang.org/x/sys v0.17.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/jpillora/backoff v1.0.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/mdlayher/socket v0.5.1 // indirect + github.com/mdlayher/vsock v1.2.1 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.59.1 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index b9fe03a..1cb5835 100644 --- a/go.sum +++ b/go.sum @@ -1,32 +1,66 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= +github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= +github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= +github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= -github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= +github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= +github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= +github.com/prometheus/exporter-toolkit v0.13.0 h1:lmA0Q+8IaXgmFRKw09RldZmZdnvu9wwcDLIXGmTPw1c= +github.com/prometheus/exporter-toolkit v0.13.0/go.mod h1:2uop99EZl80KdXhv/MxVI2181fMcwlsumFOqBecGkG0= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index f8022ed..cb92a5c 100644 --- a/main.go +++ b/main.go @@ -27,66 +27,77 @@ func main() { var controllerPassword string var intervalSeconds int var disableOptimizeInterval bool - var port int - var caFilePath string + var clientCaFilePath string var clientCertPath string var clientCertKeyPath string - var skipTLSVerification bool - var useTLS bool + var clientSkipTLSVerification bool + var useClientTLS bool var uakId string var uakPath string var uakString string + var webListenAddresses string + flag.StringVar(&webListenAddresses, "web.listen-address", "", "Address on which to expose metrics and web interface. Examples: `:2112` or `[::1]:2112` for http, `vsock://:2112` for vsock") + envflag.StringVar(&webListenAddresses, "WEB_LISTEN_ADDRESS", "", "Address on which to expose metrics and web interface. Examples: `:2112` or `[::1]:2112` for http, `vsock://:2112` for vsock") + + var webConfigFile string + flag.StringVar(&webConfigFile, "web.config.file", "", "Path to configuration file that can enable server TLS or authentication. See: https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md") + envflag.StringVar(&webConfigFile, "WEB_CONFIG_FILE", "", "Path to configuration file that can enable server TLS or authentication. See: https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md") + flag.StringVar(&controllerAddress, "controller-address", "", "Controller address to monitor (url as arg) (required)") flag.StringVar(&controllerUsername, "controller-username", "", "Controller basic auth username (username as arg)") flag.StringVar(&controllerPassword, "controller-password", "", "Controller basic auth password (password as arg)") flag.IntVar(&intervalSeconds, "interval", DEFAULT_INTERVAL_SECONDS, "Seconds to wait between data requests to controller (int as arg)") - flag.IntVar(&port, "port", 2112, "Port to server /metrics endpoint (int as arg)") + // flag.IntVar(&port, "port", 2112, "Port to server /metrics endpoint (int as arg)") flag.BoolVar(&disableOptimizeInterval, "disable-interval-optimizer", false, "Optimize interval according to /metric api requests received (no args)") - flag.BoolVar(&useTLS, "tls", false, "Enable TLS (no args)") - flag.BoolVar(&skipTLSVerification, "skip-tls-verification", false, "Skip TLS verification (no args)") - flag.StringVar(&caFilePath, "ca-cert", "", "Path to ca PEM/x509 file (cert file path as arg)") + flag.BoolVar(&useClientTLS, "client-tls", false, "Enable client TLS (no args)") + flag.BoolVar(&clientSkipTLSVerification, "client-skip-tls-verification", false, "Skip client TLS verification (no args)") + flag.StringVar(&clientCaFilePath, "client-ca-cert", "", "Path to client CA PEM/x509 file (cert file path as arg)") flag.StringVar(&clientCertPath, "client-cert", "", "Path to client cert PEM/x509 file (cert file path as arg)") flag.StringVar(&clientCertKeyPath, "client-cert-key", "", "Path to client key PEM/x509 file (cert file path as arg)") flag.StringVar(&uakId, "uak-id", "", "UAK ID you wish to use for Controller requests (string as arg)") flag.StringVar(&uakPath, "uak-path", "", "Path to the UAK file used for Controller requests (path as arg) (supersedes -uak-string)") - flag.StringVar(&uakString, "uak-string", "", "String form (cat myUAK.pem | sed '1,1d' | sed '$d' | tr -d '\n') of the key file contents for Controller requests (string as arg)") + flag.StringVar(&uakString, "uak-string", "", "String form (cat myUAK.pem | sed '1,1d' | sed '$d' | tr -d '\\n') of the key file contents for Controller requests (string as arg)") envPrefix := "ANKA_PROMETHEUS_EXPORTER_" envflag.StringVar(&controllerAddress, "CONTROLLER_ADDRESS", "", "Controller address to monitor (url as arg) (required)") envflag.StringVar(&controllerUsername, "CONTROLLER_USERNAME", "", "Controller basic auth username (username as arg)") envflag.StringVar(&controllerPassword, "CONTROLLER_PASSWORD", "", "Controller basic auth password (password as arg)") envflag.IntVar(&intervalSeconds, "INTERVAL", DEFAULT_INTERVAL_SECONDS, "Seconds to wait between data requests to controller (int as arg)") - envflag.IntVar(&port, "PORT", 2112, "Port to server /metrics endpoint (int as arg)") + // envflag.IntVar(&port, "PORT", 2112, "Port to server /metrics endpoint (int as arg)") envflag.BoolVar(&disableOptimizeInterval, "DISABLE_INTERVAL_OPTIMIZER", false, "Optimize interval according to /metric api requests received (no args)") - envflag.BoolVar(&useTLS, "TLS", false, "Enable TLS (no args)") - envflag.BoolVar(&skipTLSVerification, "SKIP_TLS_VERIFICATION", false, "Skip TLS verification (no args)") - envflag.StringVar(&caFilePath, "CA_CERT", "", "Path to ca PEM/x509 file (cert file path as arg)") + envflag.BoolVar(&useClientTLS, "CLIENT_TLS", false, "Enable client TLS (no args)") + envflag.BoolVar(&clientSkipTLSVerification, "CLIENT_SKIP_TLS_VERIFICATION", false, "Skip client TLS verification (no args)") + envflag.StringVar(&clientCaFilePath, "CLIENT_CA_CERT", "", "Path to client CA PEM/x509 file (cert file path as arg)") envflag.StringVar(&clientCertPath, "CLIENT_CERT", "", "Path to client cert PEM/x509 file (cert file path as arg)") envflag.StringVar(&clientCertKeyPath, "CLIENT_CERT_KEY", "", "Path to client key PEM/x509 file (cert file path as arg)") envflag.StringVar(&uakId, "UAK_ID", "", "UAK ID you wish to use for Controller requests (string as arg)") envflag.StringVar(&uakPath, "UAK_PATH", "", "Path to the UAK file used for Controller requests (path as arg) (supersedes -uak-string)") - envflag.StringVar(&uakString, "UAK_STRING", "", "String form (cat myUAK.pem | sed '1,1d' | sed '$d' | tr -d '\n') of the key file contents for Controller requests (string as arg)") + envflag.StringVar(&uakString, "UAK_STRING", "", "String form (cat myUAK.pem | sed '1,1d' | sed '$d' | tr -d '\\n') of the key file contents for Controller requests (string as arg)") flag.Parse() envflag.ParsePrefix(envPrefix) + if webListenAddresses == "" { + webListenAddresses = ":2112" + } + if controllerAddress == "" { - log.Fatal(fmt.Errorf("controller address not supplied (%sCONTROLLER_ADDRESS=\"http://{address}:{port}\" or --controller-address http://{address}:{port})", envPrefix)) + log.Fatal(fmt.Sprintf("controller address not supplied (%sCONTROLLER_ADDRESS=\"http://{address}:{port}\" or --controller-address http://{address}:{port})", envPrefix)) } if len(flag.Args()) > 0 { - log.Fatal(fmt.Errorf("one of your flags included a value when one wasn't needed: %s", flag.Args()[0])) + log.Fatal(fmt.Sprintf("one of your flags included a value when one wasn't needed: %s", flag.Args()[0])) } log.Info(fmt.Sprintf("Starting Prometheus Exporter for Anka (%s)", version)) - clientTLSCerts := client.TLSCerts{ - UseTLS: useTLS, - ClientCert: clientCertPath, - ClientCertKey: clientCertKeyPath, - CACert: caFilePath, - SkipTLSVerification: skipTLSVerification, + clientTLSCerts := client.ClientTLSCerts{ + UseTLS: useClientTLS, + Cert: clientCertPath, + CertKey: clientCertKeyPath, + CACert: clientCaFilePath, + SkipTLSVerification: clientSkipTLSVerification, } clientUAK := client.UAK{ @@ -97,7 +108,7 @@ func main() { client, err := client.NewClient(controllerAddress, controllerUsername, controllerPassword, intervalSeconds, clientTLSCerts, clientUAK) if err != nil { - log.Fatal(err) + log.Fatal(fmt.Sprintf("Error creating client: %s", err.Error())) } client.Init() @@ -109,7 +120,12 @@ func main() { client.Register(m.GetEvent(), m.GetEventHandler()) } - srv := server.NewServer(prometheusRegistry, port) + srv := server.NewServer( + prometheusRegistry, + webListenAddresses, + version, + webConfigFile, + ) if !disableOptimizeInterval { srv.SetIntervalUpdateFunc(client.UpdateInterval) } diff --git a/src/client/client.go b/src/client/client.go index adc894f..c34b18b 100644 --- a/src/client/client.go +++ b/src/client/client.go @@ -24,7 +24,7 @@ type Client struct { eventsMutex sync.Mutex } -func NewClient(addr, username, password string, interval int, certs TLSCerts, uak UAK) (*Client, error) { +func NewClient(addr, username, password string, interval int, certs ClientTLSCerts, uak UAK) (*Client, error) { communicator, err := NewCommunicator(addr, username, password, certs, uak) if err != nil { return nil, err @@ -41,7 +41,7 @@ func NewClient(addr, username, password string, interval int, certs TLSCerts, ua errorTimeoutSeconds: 10, } if err := c.communicator.TestConnection(); err != nil { - log.Fatal(err) + log.Fatal(fmt.Sprintf("Error testing connection: %s", err.Error())) return nil, err } return c, nil @@ -85,7 +85,7 @@ func (client *Client) initDataLoop(f func() (interface{}, error), ev events.Even log.Debug("Requesting data for: " + runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()) data, err := f() if err != nil { - log.Error(fmt.Errorf("could not get data: %+v", err)) + log.Error(fmt.Sprintf("could not get data: %+v", err)) time.Sleep(time.Duration(client.errorTimeoutSeconds) * time.Second) continue } @@ -94,7 +94,7 @@ func (client *Client) initDataLoop(f func() (interface{}, error), ev events.Even client.eventsMutex.Unlock() for _, eventHandler := range events { if err := eventHandler(data); err != nil { - log.Error(fmt.Errorf("ignoring event handler failure for event id %+v - Error: %+v", ev, err)) + log.Error(fmt.Sprintf("ignoring event handler failure for event id %+v - Error: %+v", ev, err)) } } log.Debug("Finished requesting data for: " + runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()) diff --git a/src/client/communicator.go b/src/client/communicator.go index c5f0615..dc8b428 100644 --- a/src/client/communicator.go +++ b/src/client/communicator.go @@ -44,7 +44,7 @@ func (comm *Communicator) UpdateEncodedTAPData() error { return err } -func NewCommunicator(addr, username, password string, certs TLSCerts, uak UAK) (*Communicator, error) { +func NewCommunicator(addr, username, password string, certs ClientTLSCerts, uak UAK) (*Communicator, error) { comm := &Communicator{ controllerAddress: addr, username: username, @@ -192,11 +192,11 @@ func (comm *Communicator) getData(endpoint string, repsObject types.Response) (i log.Warn("[auth::uak] uak session expired") err = comm.UpdateEncodedTAPData() if err != nil { - log.Error(fmt.Errorf("could not renew TAP for UAK: %+v", err)) + log.Error(fmt.Sprintf("could not renew TAP for UAK: %+v", err)) } repsObject, err = comm.fetchResponseData(endpoint, repsObject) if err != nil { - log.Error(fmt.Errorf("could not get data (after TAP renewal): %+v", err)) + log.Error(fmt.Sprintf("could not get data (after TAP renewal): %+v", err)) } } else { return nil, errors.New(repsObject.GetMessage()) diff --git a/src/client/tls.go b/src/client/tls.go index af8d48f..55a1f9c 100644 --- a/src/client/tls.go +++ b/src/client/tls.go @@ -8,10 +8,10 @@ import ( "os" ) -type TLSCerts struct { +type ClientTLSCerts struct { UseTLS bool - ClientCert string - ClientCertKey string + Cert string + CertKey string CACert string SkipTLSVerification bool } @@ -28,7 +28,7 @@ func appendRootCert(certFilePath string, caCertPool *x509.CertPool) error { return nil } -func setUpTLS(certs TLSCerts) error { +func setUpTLS(certs ClientTLSCerts) error { if !certs.UseTLS { return nil } @@ -45,8 +45,8 @@ func setUpTLS(certs TLSCerts) error { } } - if certs.ClientCert != "" && certs.ClientCertKey != "" { - cert, err := tls.LoadX509KeyPair(certs.ClientCert, certs.ClientCertKey) + if certs.Cert != "" && certs.CertKey != "" { + cert, err := tls.LoadX509KeyPair(certs.Cert, certs.CertKey) if err != nil { return err } diff --git a/src/log/log.go b/src/log/log.go index 2edfc86..acceff3 100644 --- a/src/log/log.go +++ b/src/log/log.go @@ -1,60 +1,52 @@ package log import ( + "log/slog" "os" - "sync" - - "github.com/sirupsen/logrus" ) -func getEnv(key, fallback string) string { - value := os.Getenv(key) - if len(value) == 0 { - return fallback +var Logger = func() *slog.Logger { + var logLevel string + if value, exists := os.LookupEnv("LOG_LEVEL"); exists { + logLevel = value + } else { + logLevel = "INFO" } - return value -} - -var Logger = GetLogger() -var once sync.Once + var level slog.Level + switch logLevel { + case "debug": + level = slog.LevelDebug + case "info": + level = slog.LevelInfo + case "warn": + level = slog.LevelWarn + case "error": + level = slog.LevelError + case "fatal": + level = slog.LevelError + default: + level = slog.LevelInfo + } + return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: level})) +}() func Info(message string) { - Logger.Infoln(message) + Logger.Info(message) } func Warn(message string) { - Logger.Warnln(message) + Logger.Warn(message) } -func Error(message error) { - Logger.Errorln(message) +func Error(message string) { + Logger.Error(message) } -func Fatal(message error) { - Logger.Fatalln(message) +func Fatal(message string) { + Logger.Error(message) + os.Exit(1) } func Debug(message string) { - Logger.Debugln(message) -} - -func GetLogger() *logrus.Logger { - var log *logrus.Logger - once.Do(func() { - log = logrus.New() - log.SetFormatter(&logrus.JSONFormatter{}) - switch getEnv("LOG_LEVEL", "info") { - case "debug": - log.SetLevel(logrus.DebugLevel) - case "info": - log.SetLevel(logrus.InfoLevel) - case "warn": - log.SetLevel(logrus.WarnLevel) - case "fatal": - log.SetLevel(logrus.FatalLevel) - case "panic": - log.SetLevel(logrus.PanicLevel) - } - }) - return log + Logger.Debug(message) } diff --git a/src/server/server.go b/src/server/server.go index 02a27ca..e6f941c 100644 --- a/src/server/server.go +++ b/src/server/server.go @@ -4,11 +4,13 @@ import ( "fmt" "math" "net/http" + "os" "sync" "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/prometheus/exporter-toolkit/web" "github.com/veertuinc/anka-prometheus-exporter/src/log" ) @@ -18,24 +20,60 @@ type Server struct { registry *prometheus.Registry intervalChangeFunc func(i int64) lock *sync.Mutex - port int + webListenAddress string + version string + configFile string } -func NewServer(promReg *prometheus.Registry, port int) *Server { +func NewServer( + promReg *prometheus.Registry, + webListenAddress string, + version string, + configFile string, +) *Server { return &Server{ lastInterval: 0, lastRequestTime: time.Now().Unix(), registry: promReg, intervalChangeFunc: nil, lock: &sync.Mutex{}, - port: port, + webListenAddress: webListenAddress, + version: version, + configFile: configFile, } } func (server *Server) Init() { - log.Info(fmt.Sprintf("Serving metrics at /metrics and :%d", server.port)) + log.Info(fmt.Sprintf("Serving metrics at %s/metrics", server.webListenAddress)) + http.HandleFunc("/metrics", server.handleRequest()) - http.ListenAndServe(fmt.Sprintf(":%d", server.port), nil) + + landingConfig := web.LandingConfig{ + HeaderColor: "#7e57c2", + Name: "Anka Prometheus Exporter", + Description: "Prometheus Exporter for Anka Build Cloud Controller", + Version: server.version, + Links: []web.LandingLinks{ + { + Address: "/metrics", + Text: "Metrics", + }, + }, + } + landingPage, err := web.NewLandingPage(landingConfig) + if err != nil { + log.Error(fmt.Sprintf("Error creating landing page: %s", err.Error())) + os.Exit(1) + } + http.Handle("/", landingPage) + + if err := web.ListenAndServe(&http.Server{}, &web.FlagConfig{ + WebListenAddresses: &[]string{server.webListenAddress}, + WebConfigFile: &server.configFile, + }, log.Logger); err != nil { + log.Error(fmt.Sprintf("Error starting web server: %s", err.Error())) + os.Exit(1) + } } func (server *Server) handleRequest() func(http.ResponseWriter, *http.Request) {