diff --git a/.github/workflows/master_merge.yaml b/.github/workflows/master_merge.yaml index 8471664..cdf7fc7 100644 --- a/.github/workflows/master_merge.yaml +++ b/.github/workflows/master_merge.yaml @@ -16,6 +16,7 @@ jobs: website: ${{ steps.filter.outputs.website }} keycloak: ${{ steps.filter.outputs.keycloak }} k8s: ${{ steps.filter.outputs.k8s }} + rmq-metrics-proxy: ${{ steps.filter.outputs.rmq-metrics-proxy }} steps: - uses: actions/checkout@v3 - uses: dorny/paths-filter@v2 @@ -30,6 +31,8 @@ jobs: - apps/keycloak/** k8s: - .k8s/prod/** + rmq-metrics-proxy: + - apps/rmq/metrics-proxy/** test-api: runs-on: ubuntu-latest @@ -167,11 +170,39 @@ jobs: context: apps/keycloak platforms: linux/arm64,linux/amd64 + build-rmq-metrics-proxy-image: + runs-on: ubuntu-latest + needs: detect-changes + if: ${{ needs.detect-changes.outputs.rmq-metrics-proxy == 'true' }} + timeout-minutes: 5 + defaults: + run: + working-directory: apps/rmq/metrics-proxy + steps: + - uses: actions/checkout@v3 + - uses: docker/setup-buildx-action@v2 + with: + platforms: linux/arm64,linux/amd64 + - uses: docker/login-action@v2 + with: + username: nikitades + password: ${{ secrets.DOCKERHUB_TOKEN }} + - uses: docker/build-push-action@v3 + with: + push: true + tags: | + nikitades/carres-rmq-metrics-proxy:latest + nikitades/carres-rmq-metrics-proxy:${{ github.sha }} + cache-from: type=gha,scope=carres-rmq-metrics-proxy + cache-to: type=gha,mode=max,scope=carres-rmq-metrics-proxy + context: apps/rmq/metrics-proxy + platforms: linux/arm64,linux/amd64 + deploy: name: Deploy runs-on: ubuntu-latest needs: [detect-changes, build-api-image, build-tgn-image, build-website-image, build-keycloak-image] - if: ${{ always() && needs.build-api-image.result != 'failure' && needs.build-tgn-image.result != 'failure' && needs.build-website-image.result != 'failure' && needs.build-keycloak-image.result != 'failure' && (github.event_name == 'pull_request' || needs.detect-changes.outputs.k8s == 'true' || needs.build-api-image.result == 'success' || needs.build-tgn-image.result == 'success' || needs.build-website-image.result == 'success' || needs.build-keycloak-image.result == 'success') }} + if: ${{ always() && needs.build-api-image.result != 'failure' && needs.build-tgn-image.result != 'failure' && needs.build-website-image.result != 'failure' && needs.build-keycloak-image.result != 'failure' && needs.build-rmq-metrics-proxy-image.result != 'failure' && (github.event_name == 'pull_request' || needs.detect-changes.outputs.k8s == 'true' || needs.build-api-image.result == 'success' || needs.build-tgn-image.result == 'success' || needs.build-website-image.result == 'success' || needs.build-keycloak-image.result == 'success' || needs.build-rmq-metrics-proxy-image.result == 'success') }} timeout-minutes: 15 env: CURRENT_TIME: @@ -209,6 +240,12 @@ jobs: propertyPath: spec.template.metadata.creationTimestamp value: ${{ steps.get-current-time.outputs.CURRENT_TIME }} commitChange: false + - uses: fjogeleit/yaml-update-action@main + with: + valueFile: .k8s/prod/carres-rmq-metrics-proxy-deployment.yaml + propertyPath: spec.template.metadata.creationTimestamp + value: ${{ steps.get-current-time.outputs.CURRENT_TIME }} + commitChange: false - uses: azure/k8s-deploy@v4 with: namespace: carres @@ -217,4 +254,5 @@ jobs: nikitades/carres-api:latest nikitades/carres-website:latest nikitades/carres-keycloak:latest + nikitades/carres-rmq-metrics-proxy:latest force: true diff --git a/.k8s/local/carres-rmq-metrics-proxy-deployment.yaml b/.k8s/local/carres-rmq-metrics-proxy-deployment.yaml new file mode 100644 index 0000000..a473080 --- /dev/null +++ b/.k8s/local/carres-rmq-metrics-proxy-deployment.yaml @@ -0,0 +1,32 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: carres-rmq-metrics-proxy-deployment + labels: + app: carres-rmq-metrics-proxy +spec: + replicas: 1 + selector: + matchLabels: + app: carres-rmq-metrics-proxy + template: + metadata: + labels: + app: carres-rmq-metrics-proxy + spec: + containers: + - name: carres-rmq-metrics-proxy + image: nikitades/carres-rmq-metrics-proxy:latest + ports: + - containerPort: 8080 + env: + - name: RMQ_ADDRESS + valueFrom: + secretKeyRef: + name: carres-rmq-metrics-proxy-secret + key: rmq-address + - name: KEYCLOAK_REALM_ADDRESS + valueFrom: + secretKeyRef: + name: carres-rmq-metrics-proxy-secret + key: keycloak-realm-address diff --git a/.k8s/local/carres-rmq-metrics-proxy-ingress.yaml b/.k8s/local/carres-rmq-metrics-proxy-ingress.yaml new file mode 100644 index 0000000..2219c13 --- /dev/null +++ b/.k8s/local/carres-rmq-metrics-proxy-ingress.yaml @@ -0,0 +1,16 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: carres-rmq-metrics-proxy +spec: + rules: + - host: rmq-metrics.carres.local + http: + paths: + - path: /metrics + pathType: ImplementationSpecific + backend: + service: + name: carres-rmq-metrics-proxy + port: + name: http-head diff --git a/.k8s/local/carres-rmq-metrics-proxy-secret.yaml b/.k8s/local/carres-rmq-metrics-proxy-secret.yaml new file mode 100644 index 0000000..09590a5 --- /dev/null +++ b/.k8s/local/carres-rmq-metrics-proxy-secret.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +metadata: + name: carres-rmq-metrics-proxy-secret +type: Opaque +data: + rmq-address: cm1xLXJhYmJpdG1x + keycloak-realm-address: Y2FycmVzLWtleWNsb2FrOjgwODAvcmVhbG1zL2NhcnJlcw== \ No newline at end of file diff --git a/.k8s/local/carres-rmq-metrics-proxy-service.yaml b/.k8s/local/carres-rmq-metrics-proxy-service.yaml new file mode 100644 index 0000000..5eeb71d --- /dev/null +++ b/.k8s/local/carres-rmq-metrics-proxy-service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: carres-rmq-metrics-proxy +spec: + selector: + app: carres-rmq-metrics-proxy + type: NodePort + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 + nodePort: 30003 + name: http-head diff --git a/.k8s/prod/carres-rmq-metrics-proxy-deployment.yaml b/.k8s/prod/carres-rmq-metrics-proxy-deployment.yaml new file mode 100644 index 0000000..a473080 --- /dev/null +++ b/.k8s/prod/carres-rmq-metrics-proxy-deployment.yaml @@ -0,0 +1,32 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: carres-rmq-metrics-proxy-deployment + labels: + app: carres-rmq-metrics-proxy +spec: + replicas: 1 + selector: + matchLabels: + app: carres-rmq-metrics-proxy + template: + metadata: + labels: + app: carres-rmq-metrics-proxy + spec: + containers: + - name: carres-rmq-metrics-proxy + image: nikitades/carres-rmq-metrics-proxy:latest + ports: + - containerPort: 8080 + env: + - name: RMQ_ADDRESS + valueFrom: + secretKeyRef: + name: carres-rmq-metrics-proxy-secret + key: rmq-address + - name: KEYCLOAK_REALM_ADDRESS + valueFrom: + secretKeyRef: + name: carres-rmq-metrics-proxy-secret + key: keycloak-realm-address diff --git a/.k8s/prod/carres-rmq-metrics-proxy-ingress.yaml b/.k8s/prod/carres-rmq-metrics-proxy-ingress.yaml new file mode 100644 index 0000000..b6b0604 --- /dev/null +++ b/.k8s/prod/carres-rmq-metrics-proxy-ingress.yaml @@ -0,0 +1,16 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: carres-rmq-metrics-proxy +spec: + rules: + - host: cars-reservation-rmq.nikitades.com + http: + paths: + - path: /metrics + pathType: ImplementationSpecific + backend: + service: + name: carres-rmq-metrics-proxy + port: + name: http-head diff --git a/.k8s/prod/carres-rmq-metrics-proxy-service.yaml b/.k8s/prod/carres-rmq-metrics-proxy-service.yaml new file mode 100644 index 0000000..5eeb71d --- /dev/null +++ b/.k8s/prod/carres-rmq-metrics-proxy-service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: carres-rmq-metrics-proxy +spec: + selector: + app: carres-rmq-metrics-proxy + type: NodePort + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 + nodePort: 30003 + name: http-head diff --git a/.k8s/prod/carres-rmq-template.yaml b/.k8s/prod/carres-rmq-template.yaml index eb1bb2d..b130e6d 100644 --- a/.k8s/prod/carres-rmq-template.yaml +++ b/.k8s/prod/carres-rmq-template.yaml @@ -344,10 +344,3 @@ spec: name: rmq-rabbitmq port: name: http-stats - - path: /metrics - pathType: ImplementationSpecific - backend: - service: - name: rmq-rabbitmq - port: - name: http-monitoring diff --git a/apps/rmq/metrics-proxy/.gitignore b/apps/rmq/metrics-proxy/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/apps/rmq/metrics-proxy/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/apps/rmq/metrics-proxy/Dockerfile b/apps/rmq/metrics-proxy/Dockerfile new file mode 100644 index 0000000..845de7a --- /dev/null +++ b/apps/rmq/metrics-proxy/Dockerfile @@ -0,0 +1,7 @@ +FROM node:18 +WORKDIR /app +COPY package*.json ./ +RUN npm ci --omit=dev +COPY . . +EXPOSE 8080 +CMD [ "node", "server.js" ] \ No newline at end of file diff --git a/apps/rmq/metrics-proxy/package-lock.json b/apps/rmq/metrics-proxy/package-lock.json new file mode 100644 index 0000000..757acdb --- /dev/null +++ b/apps/rmq/metrics-proxy/package-lock.json @@ -0,0 +1,337 @@ +{ + "name": "metrics-proxy", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "jsonwebtoken": "^9.0.2", + "jwks-rsa": "^3.1.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.4.tgz", + "integrity": "sha512-N7UDG0/xiPQa2D/XrVJXjkWbpqHCd2sBaB32ggRF2l83RhPfamgKGF8gwwqyksS95qUS5ZYF9aF+lLPRlwI2UA==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.37", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.37.tgz", + "integrity": "sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.20.tgz", + "integrity": "sha512-rOaqlkgEvOW495xErXMsmyX3WKBInbhG5eqojXYi3cGUaLoRDlXa5d52fkfWZT963AZ3v2eZ4MbKE6WpDAGVsw==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.39", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.39.tgz", + "integrity": "sha512-BiEUfAiGCOllomsRAZOiMFP7LAnrifHpt56pc4Z7l9K6ACyN06Ns1JLMBxwkfLOjJRlSf06NwWsT7yzfpaVpyQ==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.3.tgz", + "integrity": "sha512-pP0P/9BnCj1OVvQR2lF41EkDG/lWWnDyA203b/4Fmi2eTyORnBtcDoKDwjWQthELrBvWkMOrvSOnZ8OVlW6tXA==" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.4.tgz", + "integrity": "sha512-8UYapdmR0QlxgvJmyE8lP7guxD0UGVMfknsdtCFZh4ovShdBl3iOI4zdvqBHrB/IS+xUj3PSx73Qkey1fhWz+g==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.4.tgz", + "integrity": "sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw==" + }, + "node_modules/@types/node": { + "version": "20.8.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.7.tgz", + "integrity": "sha512-21TKHHh3eUHIi2MloeptJWALuCu5H7HQTdTrWIFReA8ad+aggoX+lRes3ex7/FtpC+sVUpFMQ+QTfYr74mruiQ==", + "dependencies": { + "undici-types": "~5.25.1" + } + }, + "node_modules/@types/qs": { + "version": "6.9.9", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz", + "integrity": "sha512-wYLxw35euwqGvTDx6zfY1vokBFnsK0HNrzc6xNHchxfO2hpuRg74GbkEW7e3sSmPvj0TjCDT1VCa6OtHXnubsg==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.6.tgz", + "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==" + }, + "node_modules/@types/send": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.3.tgz", + "integrity": "sha512-/7fKxvKUoETxjFUsuFlPB9YndePpxxRAOfGC/yJdc9kTjTeP5kRCTzfnE8kPUKCeyiyIZu0YQ76s50hCedI1ug==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.4.tgz", + "integrity": "sha512-aqqNfs1XTF0HDrFdlY//+SGUxmdSUbjeRXb5iaZc3x0/vMbYmdw9qvOgHWOyyLFxSSRnUuP5+724zBgfw8/WAw==", + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jose": { + "version": "4.15.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.4.tgz", + "integrity": "sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwks-rsa": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.1.0.tgz", + "integrity": "sha512-v7nqlfezb9YfHHzYII3ef2a2j1XnGeSE/bK3WfumaYCqONAIstJbrEGapz4kadScZzEt7zYCN7bucj8C0Mv/Rg==", + "dependencies": { + "@types/express": "^4.17.17", + "@types/jsonwebtoken": "^9.0.2", + "debug": "^4.3.4", + "jose": "^4.14.6", + "limiter": "^1.1.5", + "lru-memoizer": "^2.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "node_modules/lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==", + "dependencies": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "node_modules/lru-memoizer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.2.0.tgz", + "integrity": "sha512-QfOZ6jNkxCcM/BkIPnFsqDhtrazLRsghi9mBwFAzol5GCvj4EkFT899Za3+QwikCg5sRX8JstioBDwOxEyzaNw==", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/undici-types": { + "version": "5.25.3", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", + "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==" + }, + "node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + } + } +} diff --git a/apps/rmq/metrics-proxy/package.json b/apps/rmq/metrics-proxy/package.json new file mode 100644 index 0000000..c694042 --- /dev/null +++ b/apps/rmq/metrics-proxy/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "jsonwebtoken": "^9.0.2", + "jwks-rsa": "^3.1.0" + } +} diff --git a/apps/rmq/metrics-proxy/server.js b/apps/rmq/metrics-proxy/server.js new file mode 100644 index 0000000..09ebcad --- /dev/null +++ b/apps/rmq/metrics-proxy/server.js @@ -0,0 +1,31 @@ +const http = require("http"); +const https = require("https"); +const verification = require("./verify-token"); + +const server = http.createServer(async (req, res) => { + const accessTokenBearer = req.headers["authorization"]; + + if (!accessTokenBearer) { + res.writeHead(401); + return res.end(); + } + + const accessToken = accessTokenBearer.slice("Bearer ".length); + + if (!(await verification.verifyToken(accessToken, "metrics-scraper"))) { + res.writeHead(401); + return res.end(); + } + + const rmqMetricsEp = `${process.env.RMQ_ADDRESS}/metrics`; + + https.get(rmqMetricsEp, (rres) => { + res.setHeader("Content-type", rres.headers["content-type"]); + res.writeHead(rres.statusCode); + rres.on("data", res.write.bind(res)); + rres.on("end", res.end.bind(res)); + }); +}); + +console.log("starting on 8080..."); +server.listen(8080); diff --git a/apps/rmq/metrics-proxy/verify-token.js b/apps/rmq/metrics-proxy/verify-token.js new file mode 100644 index 0000000..7a8384d --- /dev/null +++ b/apps/rmq/metrics-proxy/verify-token.js @@ -0,0 +1,41 @@ +const jsonwebtoken = require("jsonwebtoken"); +const jwksrsa = require("jwks-rsa"); + +/** + * + * @param {jsonwebtoken.JwtHeader} header + * @returns {Promise} + */ +const getKey = async (header) => { + const client = new jwksrsa.JwksClient({ + jwksUri: `${process.env.KEYCLOAK_REALM_ADDRESS}/protocol/openid-connect/certs`, + }); + + return await client.getSigningKey(header.kid); +}; + +/** + * + * @param {string} token + * @param {string} requiredRole + * @returns {Promise} + */ +const verifyToken = async (token, requiredRole) => { + try { + const jwtDecoded = jsonwebtoken.decode(token, { complete: true }); + /** @type {jsonwebtoken.JwtHeader} signingkey */ + const signingKey = await getKey(jwtDecoded?.header); + /** @type {{payload: jsonwebtoken.JwtPayload & { roles: string[] }}} aaa */ + const aaa = jsonwebtoken.verify(token, signingKey.getPublicKey(), { complete: true }); + const roles = aaa.payload["roles"]; + if (!roles.includes(requiredRole)) { + throw new Error(`Role <${requiredRole}> is missing from the roles list`); + } + return true; + } catch (err) { + console.error({ err }); + return false; + } +}; + +module.exports = { verifyToken };