├── dashboard ├── cert │ ├── self-signed-root.srl │ ├── .gitignore │ ├── gen-csr-nanw.sh │ ├── v3_ext.txt │ ├── req-self-signed-root.conf │ ├── req-nanw.conf │ ├── gen-self-signed.sh │ ├── self-signed-root.crt │ ├── nanw.csr │ ├── nanw.crt │ └── nanw_eng_vmware_com.crt ├── collie │ ├── grafana-export-datasources.sh │ ├── grafana-import-datasources.sh │ └── grafana-export-dashboards.sh ├── .gitlab-ci.yml └── .gitignore ├── api-server ├── .gitignore ├── config │ ├── app-default.yaml │ └── oauth-default.yaml ├── Dockerfile ├── service │ ├── template │ │ ├── namespace.yaml │ │ ├── service-account.yaml │ │ ├── secret.yaml │ │ ├── configmap.yaml │ │ ├── kube-hunter-job.yaml │ │ ├── cluster-role-binding.yaml │ │ ├── role-binding.yaml │ │ ├── resource-quota.yaml │ │ ├── role.yaml │ │ ├── clustervpa-configmap.yaml │ │ ├── cluster-role.yaml │ │ └── kube-bench-job.yaml │ ├── persist │ │ └── persist.go │ ├── org │ │ └── org.go │ ├── auth │ │ ├── authInfo.go │ │ └── auth.go │ ├── agent-template.go │ └── oauth │ │ └── common │ │ └── common.go ├── README.md ├── Makefile ├── model │ ├── error.go │ ├── admin.go │ ├── bottle.go │ └── account.go ├── controller │ ├── controller.go │ ├── report.go │ └── portal.go ├── httputil │ └── error.go ├── util │ └── util.go ├── middleware │ └── auth.go ├── commonms │ ├── base.go │ └── healthz.go └── go.mod ├── collie ├── .eslintrc.json ├── next.config.js ├── postcss.config.js ├── src │ ├── app │ │ ├── favicon.ico │ │ ├── globals.css │ │ └── layout.tsx │ └── middleware.ts ├── .gitignore ├── tailwind.config.ts ├── public │ ├── vercel.svg │ └── next.svg ├── tsconfig.json ├── package.json └── README.md ├── deployment ├── helm-charts │ ├── .helmignore │ ├── grafana │ │ ├── templates │ │ │ ├── extra-manifests.yaml │ │ │ ├── secret-env.yaml │ │ │ ├── tests │ │ │ │ ├── test-serviceaccount.yaml │ │ │ │ ├── test-configmap.yaml │ │ │ │ ├── test-role.yaml │ │ │ │ ├── test-rolebinding.yaml │ │ │ │ ├── test-podsecuritypolicy.yaml │ │ │ │ └── test.yaml │ │ │ ├── serviceaccount.yaml │ │ │ ├── poddisruptionbudget.yaml │ │ │ ├── headless-service.yaml │ │ │ ├── rolebinding.yaml │ │ │ ├── clusterrolebinding.yaml │ │ │ ├── dashboards-json-configmap.yaml │ │ │ ├── clusterrole.yaml │ │ │ ├── image-renderer-service.yaml │ │ │ ├── pvc.yaml │ │ │ ├── secret.yaml │ │ │ ├── role.yaml │ │ │ ├── configmap-dashboard-provider.yaml │ │ │ ├── podsecuritypolicy.yaml │ │ │ ├── servicemonitor.yaml │ │ │ ├── networkpolicy.yaml │ │ │ ├── image-renderer-servicemonitor.yaml │ │ │ ├── service.yaml │ │ │ ├── hpa.yaml │ │ │ ├── image-renderer-hpa.yaml │ │ │ ├── deployment.yaml │ │ │ ├── statefulset.yaml │ │ │ ├── image-renderer-network-policy.yaml │ │ │ ├── ingress.yaml │ │ │ └── NOTES.txt │ │ ├── Makefile │ │ ├── Chart.yaml │ │ └── values-custom.yaml │ ├── es │ │ ├── Makefile │ │ ├── Chart.yaml │ │ └── values-custom.yaml │ ├── api-server │ │ ├── templates │ │ │ ├── configmap.yaml │ │ │ ├── serviceaccount.yaml │ │ │ ├── service.yaml │ │ │ ├── tests │ │ │ │ └── test-connection.yaml │ │ │ ├── secret.yaml │ │ │ ├── rbac.yaml │ │ │ ├── hpa.yaml │ │ │ ├── NOTES.txt │ │ │ ├── _helpers.tpl │ │ │ ├── ingress.yaml │ │ │ └── deployment.yaml │ │ ├── .helmignore │ │ ├── Chart.yaml │ │ └── values.yaml │ ├── prep.sh │ └── Makefile ├── scripts │ ├── .gitignore │ ├── delete-agent.sh │ ├── azure │ │ ├── delete-agent.sh │ │ ├── sc-standard.yml │ │ ├── deploy-all.sh │ │ ├── check-req.sh │ │ ├── pv-my.yml │ │ ├── deploy-server.sh │ │ ├── delete-all.sh │ │ ├── deploy-grafana.sh │ │ └── deploy-es.sh │ ├── minikube │ │ ├── delete-agent.sh │ │ ├── deploy-all.sh │ │ ├── check-req.sh │ │ ├── kill-processes-by-keyword.sh │ │ ├── deploy-server.sh │ │ ├── delete-all.sh │ │ ├── debug-image.sh │ │ ├── deploy-grafana.sh │ │ └── deploy-es.sh │ ├── deploy-agent.sh │ ├── gitlab-get-user.sh │ ├── es-add-doc.sh │ ├── .env.starter │ ├── toolbox.yaml │ ├── es-delete-old-index.sh │ ├── q.sh │ ├── es-query.sh │ ├── ingress.yaml │ ├── es-recreate-index.sh │ ├── upgrade-minikube.sh │ ├── deploy-api-server.sh │ ├── auth-gitlab.sh │ ├── bad-pod.yaml │ ├── README.md │ ├── portal.yaml │ ├── redeploy-api-server.sh │ └── job.yaml ├── README.md └── k8s │ └── azure │ └── parameters.json ├── k8s-agent ├── .gitignore ├── Dockerfile ├── README.md ├── .env.starter ├── Makefile ├── internal │ ├── config │ │ └── version.go │ ├── reporter │ │ └── collie_client_interface.go │ ├── services │ │ └── version │ │ │ └── version.go │ ├── model │ │ └── model.go │ └── commonms │ │ ├── base.go │ │ └── healthz.go ├── .gitlab-ci.yml └── go.mod ├── doc └── images │ ├── screenshot-login.png │ ├── screenshot-paired.png │ ├── screenshot-pairing.png │ └── screenshot-dashboard.png ├── NOTICE ├── .gitignore ├── CONTRIBUTING_CLA.md └── deployment.md /dashboard/cert/self-signed-root.srl: -------------------------------------------------------------------------------- 1 | B998C67DE151E673 2 | -------------------------------------------------------------------------------- /api-server/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | collie-api-server 3 | .env 4 | 5 | -------------------------------------------------------------------------------- /collie/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /dashboard/cert/.gitignore: -------------------------------------------------------------------------------- 1 | #*.key 2 | #*.crt 3 | #*.csr 4 | #*.srl 5 | -------------------------------------------------------------------------------- /deployment/helm-charts/.helmignore: -------------------------------------------------------------------------------- 1 | tests/ 2 | .pytest_cache/ 3 | 4 | -------------------------------------------------------------------------------- /deployment/scripts/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .env.local 3 | .env.collie 4 | 5 | -------------------------------------------------------------------------------- /deployment/README.md: -------------------------------------------------------------------------------- 1 | # deployment 2 | 3 | Scripts for deployment and integration test. -------------------------------------------------------------------------------- /k8s-agent/.gitignore: -------------------------------------------------------------------------------- 1 | .terraform 2 | .idea 3 | .run 4 | *.iml 5 | bin 6 | .env 7 | collie-agent 8 | 9 | -------------------------------------------------------------------------------- /deployment/scripts/delete-agent.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | kubectl delete namespace collie-agent --wait=true 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /collie/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /collie/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /collie/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/compliance-dashboard-for-kubernetes/main/collie/src/app/favicon.ico -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/extra-manifests.yaml: -------------------------------------------------------------------------------- 1 | {{ range .Values.extraObjects }} 2 | --- 3 | {{ tpl (toYaml .) $ }} 4 | {{ end }} 5 | -------------------------------------------------------------------------------- /doc/images/screenshot-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/compliance-dashboard-for-kubernetes/main/doc/images/screenshot-login.png -------------------------------------------------------------------------------- /doc/images/screenshot-paired.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/compliance-dashboard-for-kubernetes/main/doc/images/screenshot-paired.png -------------------------------------------------------------------------------- /doc/images/screenshot-pairing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/compliance-dashboard-for-kubernetes/main/doc/images/screenshot-pairing.png -------------------------------------------------------------------------------- /k8s-agent/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.17.2 2 | ARG TARGETARCH 3 | COPY bin/collie-agent-$TARGETARCH /usr/local/bin/collie-agent 4 | CMD ["collie-agent"] 5 | -------------------------------------------------------------------------------- /doc/images/screenshot-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnissa-archive/compliance-dashboard-for-kubernetes/main/doc/images/screenshot-dashboard.png -------------------------------------------------------------------------------- /api-server/config/app-default.yaml: -------------------------------------------------------------------------------- 1 | agent_image: "collie.azurecr.io/collie-agent:1" 2 | grafana_url: "https://collie.eng.omnissa.com/d/qIbLYbT4z/k8s-compliance-report" 3 | -------------------------------------------------------------------------------- /deployment/scripts/azure/delete-agent.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | kubectl get ns/collie-agent > /dev/null 2>&1 4 | if [ $? -eq 0 ]; then 5 | kubectl delete namespace collie-agent --wait 6 | fi 7 | -------------------------------------------------------------------------------- /deployment/scripts/minikube/delete-agent.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | kubectl get ns/collie-agent > /dev/null 2>&1 4 | if [ $? -eq 0 ]; then 5 | kubectl delete namespace collie-agent --wait 6 | fi 7 | -------------------------------------------------------------------------------- /api-server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.17.2 2 | ARG TARGETARCH 3 | COPY bin/collie-api-server-$TARGETARCH /usr/local/bin/collie-api-server 4 | COPY assets /assets 5 | COPY config/*.yaml /config/ 6 | CMD ["collie-api-server"] 7 | -------------------------------------------------------------------------------- /dashboard/collie/grafana-export-datasources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mkdir -p ./grafana-data-sources 4 | curl -k "$COLLIE_GRAFANA_URL/api/datasources" -u $COLLIE_GRAFANA_CREDENTIAL |jq -c -M '.[]'|split -l 1 - ./grafana-data-sources/ 5 | -------------------------------------------------------------------------------- /deployment/scripts/deploy-agent.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | curl -skH "Authorization: Bearer elastic:16b9f5de2c23718edbf713731584fbb3" "https://collie.eng.omnissa.com/collie/api/v1/onboarding/agent.yaml?provider=AKS&aid=a63aefd2af7028be" | kubectl apply -f - 4 | -------------------------------------------------------------------------------- /dashboard/cert/gen-csr-nanw.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -f nanw.key 4 | rm -f nanw.crt 5 | rm -f nanw.csr 6 | 7 | openssl req -newkey rsa:2048 -nodes -sha256 -keyout nanw.key -new -out nanw.csr -config req-nanw.conf 8 | 9 | openssl req -text -noout -verify -in nanw.csr 10 | -------------------------------------------------------------------------------- /deployment/scripts/gitlab-get-user.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source .env 4 | 5 | ACCESS_TOKEN=$(source auth-gitlab.sh | jq -r '.access_token') 6 | 7 | echo $ACCESS_TOKEN 8 | 9 | curl -s -H "Authorization: Bearer $ACCESS_TOKEN" https://gitlab.eng.omnissa.com/api/v4/user 10 | 11 | -------------------------------------------------------------------------------- /api-server/service/template/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: collie-agent 5 | labels: 6 | app.kubernetes.io/name: collie 7 | app.kubernetes.io/instance: collie 8 | app.kubernetes.io/version: "v1" 9 | app.kubernetes.io/managed-by: collie -------------------------------------------------------------------------------- /dashboard/collie/grafana-import-datasources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for i in ./grafana-data-sources/*; do \ 4 | curl -k -X "POST" "$COLLIE_GRAFANA_URL/api/datasources" \ 5 | -H "Content-Type: application/json" \ 6 | --user $COLLIE_GRAFANA_CREDENTIAL \ 7 | --data-binary @$i 8 | done 9 | -------------------------------------------------------------------------------- /deployment/scripts/es-add-doc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | curl -k -u $ES_KEY -X POST -H "Content-Type: application/json" -d ' 4 | { 5 | "field1": "value1", 6 | "field2": "value2", 7 | "n1": { 8 | "n11": { 9 | "v1": 1 10 | }, 11 | "n12": 3 12 | } 13 | }' $ES_URL/t1/_doc 14 | 15 | -------------------------------------------------------------------------------- /api-server/README.md: -------------------------------------------------------------------------------- 1 | # Collie API server 2 | 3 | Gen doc 4 | 5 | ```console 6 | $ go get -u github.com/swaggo/swag/cmd/swag 7 | $ swag init 8 | ``` 9 | 10 | Run app 11 | 12 | ```console 13 | $ go run main.go 14 | ``` 15 | 16 | [open swagger](http://localhost:8080/swagger/index.html) 17 | 18 | -------------------------------------------------------------------------------- /k8s-agent/README.md: -------------------------------------------------------------------------------- 1 | # Collie API server 2 | 3 | Gen doc 4 | 5 | ```console 6 | $ go get -u github.com/swaggo/swag/cmd/swag 7 | $ swag init 8 | ``` 9 | 10 | Run app 11 | 12 | ```console 13 | $ go run main.go 14 | ``` 15 | 16 | [open swagger](http://localhost:8080/swagger/index.html) 17 | 18 | -------------------------------------------------------------------------------- /deployment/helm-charts/es/Makefile: -------------------------------------------------------------------------------- 1 | default: test 2 | 3 | RELEASE := helm-es-minikube 4 | TIMEOUT := 1200s 5 | 6 | install: 7 | helm upgrade --wait --timeout=$(TIMEOUT) --install --values values.yaml $(RELEASE) ../../ 8 | 9 | test: install 10 | helm test $(RELEASE) 11 | 12 | purge: 13 | helm del $(RELEASE) 14 | -------------------------------------------------------------------------------- /deployment/scripts/azure/sc-standard.yml: -------------------------------------------------------------------------------- 1 | allowVolumeExpansion: true 2 | apiVersion: storage.k8s.io/v1 3 | kind: StorageClass 4 | metadata: 5 | name: standard 6 | parameters: 7 | skuname: StandardSSD_LRS 8 | provisioner: disk.csi.azure.com 9 | reclaimPolicy: Retain 10 | volumeBindingMode: WaitForFirstConsumer 11 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/Makefile: -------------------------------------------------------------------------------- 1 | default: test 2 | 3 | RELEASE := helm-es-minikube 4 | TIMEOUT := 1200s 5 | 6 | install: 7 | helm upgrade --wait --timeout=$(TIMEOUT) --install --values values.yaml $(RELEASE) ../../ 8 | 9 | test: install 10 | helm test $(RELEASE) 11 | 12 | purge: 13 | helm del $(RELEASE) 14 | -------------------------------------------------------------------------------- /api-server/service/template/service-account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: agent 5 | namespace: collie-agent 6 | labels: 7 | app.kubernetes.io/name: agent 8 | app.kubernetes.io/instance: agent 9 | app.kubernetes.io/version: "v1" 10 | app.kubernetes.io/managed-by: collie -------------------------------------------------------------------------------- /deployment/scripts/minikube/deploy-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source ./delete-all.sh 4 | source ./check-req.sh 5 | 6 | kubectl create namespace collie-server 7 | kubectl apply -f persistent-volume.yml --namespace collie-server 8 | 9 | source deploy-es.sh 10 | source deploy-grafana.sh 11 | source deploy-server.sh 12 | 13 | echo 14 | echo Complete. 15 | 16 | 17 | -------------------------------------------------------------------------------- /api-server/service/template/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: agent 5 | namespace: collie-agent 6 | labels: 7 | app.kubernetes.io/name: agent 8 | app.kubernetes.io/instance: agent 9 | app.kubernetes.io/version: "v1" 10 | app.kubernetes.io/managed-by: collie 11 | 12 | data: 13 | API_KEY: {{.ApiKey}} 14 | ES_KEY: {{.EsKey}} -------------------------------------------------------------------------------- /deployment/scripts/.env.starter: -------------------------------------------------------------------------------- 1 | export API_URL=https://collie.eng.omnissa.com/collie 2 | export ES_URL=https://collie.eng.omnissa.com:9200 3 | export COLLIE_ES_USERNAME= 4 | export COLLIE_ES_PASSWORD= 5 | export ES_KEY= 6 | export COLLIE_ES_URL=$ES_URL 7 | 8 | export OAUTH_CSP_CLIENTID= 9 | export OAUTH_CSP_CLIENTSECRET= 10 | export OAUTH_GITLAB_CLIENTID= 11 | export OAUTH_GITLAB_CLIENTSECRET= 12 | -------------------------------------------------------------------------------- /deployment/helm-charts/api-server/templates/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: {{ include "..fullname" . }} 5 | labels: 6 | {{- include "..labels" . | nindent 4 }} 7 | 8 | data: 9 | COLLIE_URL: {{ .Values.collie.url }} 10 | #API_URL: {{ .Values.collie.url }}/collie 11 | ES_URL: {{ .Values.es.url }} 12 | GRAFANA_URL: {{ .Values.grafana.url }} 13 | -------------------------------------------------------------------------------- /deployment/scripts/azure/deploy-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source ./delete-all.sh 4 | source ./check-req.sh 5 | 6 | kubectl create namespace collie-server 7 | kubectl apply -f sc-standard.yml 8 | kubectl apply -f persistent-volume.yml --namespace collie-server 9 | 10 | source deploy-es.sh 11 | source deploy-grafana.sh 12 | source deploy-server.sh 13 | 14 | echo 15 | echo Complete. 16 | 17 | 18 | -------------------------------------------------------------------------------- /api-server/service/template/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: agent 5 | namespace: collie-agent 6 | labels: 7 | app.kubernetes.io/name: agent 8 | app.kubernetes.io/instance: agent 9 | app.kubernetes.io/version: "v1" 10 | app.kubernetes.io/managed-by: collie 11 | 12 | data: 13 | API_URL: {{.ApiUrl}} 14 | ES_URL: {{.EsUrl}} 15 | PROVIDER: {{.Provider}} -------------------------------------------------------------------------------- /deployment/helm-charts/api-server/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "..serviceAccountName" . }} 6 | labels: 7 | {{- include "..labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Omnissa, LLC. 2 | 3 | This product is licensed to you under the Apache License, V2.0 (the "License"). You may not use this product except in compliance with the License. 4 | 5 | This product may include a number of subcomponents with separate copyright notices and license terms. Your use of these subcomponents is subject to the terms and conditions of the subcomponent's license, as noted in the LICENSE file. -------------------------------------------------------------------------------- /deployment/helm-charts/es/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | description: Official Elastic helm chart for Elasticsearch 3 | home: https://github.com/elastic/helm-charts 4 | maintainers: 5 | - email: helm-charts@elastic.co 6 | name: Elastic 7 | name: elasticsearch 8 | version: 8.5.1 9 | appVersion: 8.5.1 10 | sources: 11 | - https://github.com/elastic/elasticsearch 12 | icon: https://helm.elastic.co/icons/elasticsearch.png 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | 18 | # Go workspace file 19 | go.work 20 | go.work.sum 21 | 22 | .env 23 | .secret 24 | -------------------------------------------------------------------------------- /k8s-agent/.env.starter: -------------------------------------------------------------------------------- 1 | export API_KEY= 2 | export API_URL=https://collie.eng.omnissa.com/collie 3 | export ES_URL=https://collie.eng.omnissa.com:9200 4 | export ES_KEY= 5 | 6 | export PROVIDER=AKS 7 | export AGENTID=demo 8 | 9 | # for local testing without having to run the agent inside k8s 10 | export KUBECONFIG=/Users/nanw/.kube/config 11 | 12 | # tuning to make testing cycle faster 13 | export CONTROLLER_INITIAL_SLEEP_DURATION=3s 14 | -------------------------------------------------------------------------------- /dashboard/cert/v3_ext.txt: -------------------------------------------------------------------------------- 1 | basicConstraints = critical, CA:FALSE 2 | subjectKeyIdentifier = hash 3 | authorityKeyIdentifier = keyid,issuer 4 | keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment, keyAgreement, dataEncipherment 5 | extendedKeyUsage = critical, serverAuth 6 | subjectAltName = @alt_names 7 | 8 | [alt_names] 9 | DNS.1 = nanw.eng.omnissa.com 10 | DNS.2 = collie.eng.omnissa.com 11 | -------------------------------------------------------------------------------- /deployment/helm-charts/api-server/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "..fullname" . }} 5 | labels: 6 | {{- include "..labels" . | nindent 4 }} 7 | spec: 8 | type: {{ .Values.service.type }} 9 | ports: 10 | - port: {{ .Values.service.port }} 11 | targetPort: http 12 | protocol: TCP 13 | name: http 14 | selector: 15 | {{- include "..selectorLabels" . | nindent 4 }} 16 | -------------------------------------------------------------------------------- /api-server/service/template/kube-hunter-job.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: kube-hunter 5 | namespace: collie-agent 6 | spec: 7 | template: 8 | metadata: 9 | labels: 10 | app: kube-hunter 11 | spec: 12 | containers: 13 | - name: kube-hunter 14 | image: collie.azurecr.io/kube-hunter:0.6.8 15 | command: ["kube-hunter"] 16 | args: ["--pod","--report=json"] 17 | restartPolicy: Never -------------------------------------------------------------------------------- /deployment/scripts/toolbox.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: toolbox 5 | namespace: default 6 | labels: 7 | app: toolbox 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: toolbox 13 | template: 14 | metadata: 15 | labels: 16 | app: toolbox 17 | spec: 18 | containers: 19 | - name: toolbox 20 | image: ubuntu:latest 21 | imagePullPolicy: IfNotPresent 22 | -------------------------------------------------------------------------------- /dashboard/cert/req-self-signed-root.conf: -------------------------------------------------------------------------------- 1 | [req] 2 | distinguished_name = req_distinguished_name 3 | req_extensions = v3_ca 4 | prompt = no 5 | [req_distinguished_name] 6 | C = US 7 | ST = CA 8 | L = Palo Alto 9 | O = Omnissa 10 | OU = EUC 11 | CN = nanw-dev 12 | [v3_ca] 13 | basicConstraints = critical, CA:TRUE 14 | subjectKeyIdentifier = hash 15 | authorityKeyIdentifier = keyid:always, issuer:always 16 | keyUsage = critical, cRLSign, digitalSignature, keyCertSign 17 | -------------------------------------------------------------------------------- /deployment/helm-charts/api-server/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "..fullname" . }}-test-connection" 5 | labels: 6 | {{- include "..labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test 9 | spec: 10 | containers: 11 | - name: wget 12 | image: busybox 13 | command: ['wget'] 14 | args: ['{{ include "..fullname" . }}:{{ .Values.service.port }}'] 15 | restartPolicy: Never 16 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/secret-env.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.envRenderSecret }} 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: {{ include "grafana.fullname" . }}-env 6 | namespace: {{ include "grafana.namespace" . }} 7 | labels: 8 | {{- include "grafana.labels" . | nindent 4 }} 9 | type: Opaque 10 | data: 11 | {{- range $key, $val := .Values.envRenderSecret }} 12 | {{ $key }}: {{ tpl ($val | toString) $ | b64enc | quote }} 13 | {{- end }} 14 | {{- end }} 15 | -------------------------------------------------------------------------------- /deployment/scripts/es-delete-old-index.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | curl -k -u $ES_USERNAME:$ES_PASSWORD -X DELETE "$ES_URL/collie-k8s-cluster-info-elastic" 4 | curl -k -u $ES_USERNAME:$ES_PASSWORD -X DELETE "$ES_URL/collie-k8s-compliance-elastic" 5 | curl -k -u $ES_USERNAME:$ES_PASSWORD -X DELETE "$ES_URL/collie-k8s-resource-elastic" 6 | curl -k -u $ES_USERNAME:$ES_PASSWORD -X DELETE "$ES_URL/collie-k8s-activity-elastic" 7 | #curl -k -u $ES_USERNAME:$ES_PASSWORD -X DELETE "$ES_URL/collie-k8s-elastic" 8 | -------------------------------------------------------------------------------- /collie/src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { authMiddleware } from "@clerk/nextjs"; 2 | 3 | // This example protects all routes including api/trpc routes 4 | // Please edit this to allow other routes to be public as needed. 5 | // See https://clerk.com/docs/references/nextjs/auth-middleware for more information about configuring your middleware 6 | export default authMiddleware({}); 7 | 8 | export const config = { 9 | matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'], 10 | }; 11 | -------------------------------------------------------------------------------- /deployment/helm-charts/api-server/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /deployment/scripts/azure/check-req.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ping -c 1 collie-dev.org > /dev/null 2>&1 4 | 5 | if [ $? -ne 0 ]; then 6 | echo "Error: collie-dev.org is not set to local public IP." 7 | echo 8 | echo -------------------------- 9 | echo Manual operation needed: 10 | echo 1. Use ifconfig to identify local machine public IP. 11 | echo 2. Update /etc/hosts, add 'collie-dev.org' to local host public IP. 12 | echo -------------------------- 13 | echo 14 | 15 | exit 1 16 | fi 17 | -------------------------------------------------------------------------------- /deployment/scripts/q.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | source .env 3 | 4 | for i in 1 2 5 | do 6 | RESP=$(curl -skS -u $COLLIE_ES_USERNAME:$COLLIE_ES_PASSWORD "$COLLIE_ES_URL/collie-k8s-activity-elastic/_search?q=a:9e888726a3908753" | jq ".hits.hits[0]._source") 7 | 8 | if [ "$RESP" = "null" ]; then 9 | echo "Not ready yet..." 10 | sleep 10 11 | else 12 | echo "$RESP" 13 | echo "OK" 14 | exit 0 15 | fi 16 | done 17 | 18 | echo "Failed waiting for agent report in ES" 19 | exit 1 20 | -------------------------------------------------------------------------------- /deployment/scripts/minikube/check-req.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ping -c 1 collie-dev.org > /dev/null 2>&1 4 | 5 | if [ $? -ne 0 ]; then 6 | echo "Error: collie-dev.org is not set to local public IP." 7 | echo 8 | echo -------------------------- 9 | echo Manual operation needed: 10 | echo 1. Use ifconfig to identify local machine public IP. 11 | echo 2. Update /etc/hosts, add 'collie-dev.org' to local host public IP. 12 | echo -------------------------- 13 | echo 14 | 15 | exit 1 16 | fi 17 | -------------------------------------------------------------------------------- /deployment/scripts/minikube/kill-processes-by-keyword.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | keyword="$1" 4 | 5 | # Get the process IDs of all processes matching the keyword 6 | pids=$(pgrep -f "$keyword") 7 | 8 | if [ -z "$pids" ]; then 9 | echo "No processes found matching the keyword '$keyword'" 10 | else 11 | # Kill each process 12 | for pid in $pids; do 13 | echo "Killing process: $pid" 14 | kill "$pid" 15 | done 16 | echo "All processes matching the keyword '$keyword' have been killed." 17 | fi 18 | 19 | -------------------------------------------------------------------------------- /deployment/scripts/es-query.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ORG_ID=elastic 4 | INDEX_URL=$ES_URL/collie-k8s-$ORG_ID/_search 5 | 6 | curl -k -u $ES_KEY -XGET -H "Content-Type: application/json" -d '{ 7 | "query": { 8 | "bool": { 9 | "must": [ 10 | { 11 | "range": { 12 | "@timestamp": { 13 | "lt": "2023-06-08T22:52:13Z" 14 | } 15 | } 16 | }, 17 | { 18 | "term": { 19 | "a": "demo" 20 | } 21 | } 22 | ] 23 | } 24 | } 25 | }' $INDEX_URL 26 | 27 | -------------------------------------------------------------------------------- /deployment/scripts/azure/pv-my.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolume 3 | metadata: 4 | name: my-pv 5 | spec: 6 | capacity: 7 | storage: 5Gi # Specify the storage capacity 8 | volumeMode: Filesystem # or Block for raw block devices 9 | accessModes: 10 | - ReadWriteOnce # Read-write access for a single node 11 | persistentVolumeReclaimPolicy: Retain # or Delete 12 | storageClassName: standard # Use a StorageClass, if applicable 13 | hostPath: 14 | path: /mnt/data # Host path for the volume 15 | -------------------------------------------------------------------------------- /api-server/service/template/cluster-role-binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: agent 5 | labels: 6 | app.kubernetes.io/name: agent 7 | app.kubernetes.io/instance: agent 8 | app.kubernetes.io/version: "v1" 9 | app.kubernetes.io/managed-by: collie 10 | 11 | roleRef: 12 | apiGroup: rbac.authorization.k8s.io 13 | kind: ClusterRole 14 | name: agent 15 | subjects: 16 | - kind: ServiceAccount 17 | name: agent 18 | namespace: collie-agent -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/tests/test-serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.testFramework.enabled .Values.serviceAccount.create }} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | labels: 6 | {{- include "grafana.labels" . | nindent 4 }} 7 | name: {{ include "grafana.serviceAccountNameTest" . }} 8 | namespace: {{ include "grafana.namespace" . }} 9 | annotations: 10 | "helm.sh/hook": test-success 11 | "helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded" 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /api-server/service/template/role-binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: agent 5 | labels: 6 | app.kubernetes.io/name: agent 7 | app.kubernetes.io/instance: agent 8 | app.kubernetes.io/version: "v1" 9 | app.kubernetes.io/managed-by: collie 10 | namespace: collie-agent 11 | 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: Role 15 | name: agent 16 | subjects: 17 | - kind: ServiceAccount 18 | name: agent 19 | namespace: collie-agent -------------------------------------------------------------------------------- /collie/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /deployment/helm-charts/es/values-custom.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Shrink default JVM heap. 3 | esJavaOpts: "-Xmx256m -Xms256m" 4 | 5 | # Allocate smaller chunks of memory per pod. 6 | resources: 7 | requests: 8 | cpu: "1000m" 9 | memory: "512M" 10 | limits: 11 | cpu: "1000m" 12 | memory: "1024M" 13 | 14 | # Request smaller persistent volumes. 15 | volumeClaimTemplate: 16 | accessModes: [ "ReadWriteOnce" ] 17 | # for minikube: standard 18 | # for AKS: managed-csi 19 | storageClassName: "standard" 20 | resources: 21 | requests: 22 | storage: 100M 23 | -------------------------------------------------------------------------------- /deployment/helm-charts/prep.sh: -------------------------------------------------------------------------------- 1 | brew install kubectl 2 | brew upgrade kubectl 3 | brew link --overwrite kubernetes-cli 4 | brew install helm 5 | helm repo add grafana https://grafana.github.io/helm-charts 6 | helm repo add elastic https://helm.elastic.co 7 | 8 | brew unlink minikube 9 | brew install minikube 10 | brew link minikube 11 | 12 | minikube delete 13 | minikube config set cpus 4 14 | minikube config set memory 4096 15 | minikube start 16 | minikube addons enable default-storageclass 17 | minikube addons enable storage-provisioner 18 | minikube addons enable ingress 19 | 20 | -------------------------------------------------------------------------------- /k8s-agent/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "-s -w" -o bin/collie-agent-amd64 . 3 | docker build -t collie.azurecr.io/collie-agent:1 . 4 | 5 | generate: 6 | go generate ./... 7 | 8 | push: 9 | docker push collie.azurecr.io/collie-agent:1 10 | 11 | deploy: 12 | cat deployment.yaml | envsubst | kubectl apply -f - 13 | 14 | SHELL := /bin/bash 15 | run: 16 | source ./.env && go run . 17 | 18 | test: 19 | go test ./... -race 20 | 21 | lint: 22 | go vet ./... 23 | gofmt -w -s . 24 | golangci-lint run 25 | 26 | release: build push 27 | -------------------------------------------------------------------------------- /api-server/service/template/resource-quota.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ResourceQuota 3 | metadata: 4 | name: agent-critical-pods 5 | namespace: collie-agent 6 | labels: 7 | app.kubernetes.io/name: agent 8 | app.kubernetes.io/instance: agent 9 | app.kubernetes.io/version: "v1" 10 | app.kubernetes.io/managed-by: collie 11 | spec: 12 | scopeSelector: 13 | matchExpressions: 14 | - operator: In 15 | scopeName: PriorityClass 16 | values: 17 | # Required to ensure agent is always running to provide autoscaling capabilities. 18 | - system-cluster-critical -------------------------------------------------------------------------------- /collie/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'tailwindcss' 2 | 3 | const config: Config = { 4 | content: [ 5 | './src/pages/**/*.{js,ts,jsx,tsx,mdx}', 6 | './src/components/**/*.{js,ts,jsx,tsx,mdx}', 7 | './src/app/**/*.{js,ts,jsx,tsx,mdx}', 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 13 | 'gradient-conic': 14 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | } 20 | export default config 21 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create }} 2 | {{- $root := . -}} 3 | apiVersion: v1 4 | kind: ServiceAccount 5 | metadata: 6 | labels: 7 | {{- include "grafana.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.labels }} 9 | {{- toYaml . | nindent 4 }} 10 | {{- end }} 11 | {{- with .Values.serviceAccount.annotations }} 12 | annotations: 13 | {{- tpl (toYaml . | nindent 4) $root }} 14 | {{- end }} 15 | name: {{ include "grafana.serviceAccountName" . }} 16 | namespace: {{ include "grafana.namespace" . }} 17 | {{- end }} 18 | -------------------------------------------------------------------------------- /api-server/service/template/role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: Role 3 | metadata: 4 | name: agent 5 | namespace: collie-agent 6 | labels: 7 | app.kubernetes.io/name: agent 8 | app.kubernetes.io/instance: agent 9 | app.kubernetes.io/version: "v1" 10 | app.kubernetes.io/managed-by: collie 11 | rules: 12 | # --- 13 | # Required for proportional vertical cluster autoscaler to adjust agent requests/limits. 14 | # --- 15 | - apiGroups: 16 | - "apps" 17 | resources: 18 | - deployments 19 | resourceNames: 20 | - agent 21 | verbs: 22 | - patch -------------------------------------------------------------------------------- /collie/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /deployment/scripts/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: api-server 5 | namespace: collie-server 6 | spec: 7 | rules: 8 | - http: 9 | paths: 10 | - path: /portal 11 | pathType: Prefix 12 | backend: 13 | service: 14 | name: portal 15 | port: 16 | name: http 17 | - path: / 18 | pathType: Prefix 19 | backend: 20 | service: 21 | name: api-server 22 | port: 23 | name: http 24 | 25 | -------------------------------------------------------------------------------- /deployment/scripts/minikube/deploy-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | helm list -n collie-server -a | grep api-server > /dev/null 2>&1 4 | if [ $? -eq 0 ]; then 5 | helm uninstall -n collie-server api-server --wait 6 | fi 7 | 8 | helm install -n collie-server api-server ./api-server --wait 9 | 10 | sleep 10 11 | 12 | source ./kill-processes-by-keyword.sh "kubectl port-forward -n collie-server --address 0.0.0.0 services/api-server" 13 | kubectl port-forward -n collie-server --address 0.0.0.0 services/api-server 8080:8080 & 14 | 15 | sleep 2 16 | 17 | echo 18 | echo Open browser: http://collie-dev.org:8080/collie/portal/login 19 | echo 20 | 21 | -------------------------------------------------------------------------------- /dashboard/cert/req-nanw.conf: -------------------------------------------------------------------------------- 1 | [req] 2 | distinguished_name = req_distinguished_name 3 | req_extensions = v3_req 4 | prompt = no 5 | [req_distinguished_name] 6 | C = US 7 | ST = CA 8 | L = Palo Alto 9 | O = Omnissa 10 | OU = EUC 11 | CN = nanw.eng.omnissa.com 12 | [v3_req] 13 | basicConstraints = critical, CA:FALSE 14 | subjectKeyIdentifier = hash 15 | keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment, keyAgreement, dataEncipherment 16 | extendedKeyUsage = critical, serverAuth 17 | subjectAltName = @alt_names 18 | [alt_names] 19 | DNS.1 = nanw.eng.omnissa.com 20 | DNS.2 = collie.eng.omnissa.com 21 | -------------------------------------------------------------------------------- /deployment/helm-charts/api-server/templates/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: {{ include "..fullname" . }} 5 | labels: 6 | {{- include "..labels" . | nindent 4 }} 7 | data: 8 | OAUTH_CSP_CLIENTID: {{ .Values.oauth.csp_clientid | b64enc }} 9 | OAUTH_CSP_CLIENTSECRET: {{ .Values.oauth.csp_clientsecret | b64enc }} 10 | OAUTH_GITLAB_CLIENTID: {{ .Values.oauth.gitlab_clientid | b64enc }} 11 | OAUTH_GITLAB_CLIENTSECRET: {{ .Values.oauth.gitlab_clientsecret | b64enc }} 12 | OAUTH_GOOGLE_CLIENTID: {{ .Values.oauth.google_clientid | b64enc }} 13 | OAUTH_GOOGLE_CLIENTSECRET: {{ .Values.oauth.google_clientsecret | b64enc }} -------------------------------------------------------------------------------- /deployment/scripts/azure/deploy-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | helm list -n collie-server -a | grep api-server > /dev/null 2>&1 4 | if [ $? -eq 0 ]; then 5 | helm uninstall -n collie-server api-server --wait 6 | fi 7 | 8 | cd ../../helm-charts 9 | helm install -n collie-server api-server ./api-server --wait 10 | 11 | sleep 10 12 | 13 | #source ./kill-processes-by-keyword.sh "kubectl port-forward -n collie-server --address 0.0.0.0 services/api-server" 14 | #kubectl port-forward -n collie-server --address 0.0.0.0 services/api-server 8080:8080 & 15 | 16 | sleep 2 17 | 18 | echo 19 | echo Open browser: http://collie-dev.org:8080/collie/portal/login 20 | echo 21 | 22 | -------------------------------------------------------------------------------- /collie/src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient( 22 | to bottom, 23 | transparent, 24 | rgb(var(--background-end-rgb)) 25 | ) 26 | rgb(var(--background-start-rgb)); 27 | } 28 | -------------------------------------------------------------------------------- /collie/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import './globals.css' 2 | import type { Metadata } from 'next' 3 | import { Inter } from 'next/font/google' 4 | import { ClerkProvider } from '@clerk/nextjs' 5 | 6 | const inter = Inter({ subsets: ['latin'] }) 7 | 8 | export const metadata: Metadata = { 9 | title: 'Collie - K8s Compliance Dashboard', 10 | description: 'K8s Compliance Dashboard', 11 | } 12 | 13 | export default function RootLayout({ 14 | children, 15 | }: { 16 | children: React.ReactNode 17 | }) { 18 | return ( 19 | 20 | 21 | {children} 22 | 23 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /deployment/scripts/azure/delete-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source ./delete-agent.sh 4 | 5 | helm list -n collie-server -a | grep api-server > /dev/null 2>&1 6 | if [ $? -eq 0 ]; then 7 | helm uninstall -n collie-server api-server --wait 8 | fi 9 | helm list -n collie-server -a | grep grafana > /dev/null 2>&1 10 | if [ $? -eq 0 ]; then 11 | helm uninstall -n collie-server grafana --wait 12 | fi 13 | helm list -n collie-server -a | grep es > /dev/null 2>&1 14 | if [ $? -eq 0 ]; then 15 | helm uninstall -n collie-server es --wait 16 | fi 17 | 18 | kubectl get ns/collie-server > /dev/null 2>&1 19 | if [ $? -eq 0 ]; then 20 | kubectl delete namespace collie-server --wait 21 | fi 22 | 23 | 24 | -------------------------------------------------------------------------------- /deployment/scripts/es-recreate-index.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ORG_ID=elastic 4 | INDEX_URL=$ES_URL/collie-k8s-$ORG_ID 5 | 6 | curl -k -u $ES_KEY -X DELETE "$INDEX_URL" 7 | 8 | curl -k -u $ES_KEY -X PUT -H "Content-Type: application/json" -d ' 9 | { 10 | "mappings": { 11 | "properties": { 12 | "resource": { 13 | "dynamic": false, 14 | "properties": { 15 | "metadata": { 16 | "properties": { 17 | "name": { 18 | "type": "text" 19 | }, 20 | "namespace": { 21 | "type": "text" 22 | } 23 | } 24 | } 25 | } 26 | } 27 | } 28 | } 29 | }' $INDEX_URL 30 | 31 | -------------------------------------------------------------------------------- /collie/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /collie/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "collie", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@clerk/nextjs": "^4.24.2", 13 | "next": "latest", 14 | "react": "latest", 15 | "react-dom": "latest" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "latest", 19 | "@types/react": "latest", 20 | "@types/react-dom": "latest", 21 | "autoprefixer": "latest", 22 | "eslint": "latest", 23 | "eslint-config-next": "latest", 24 | "postcss": "latest", 25 | "tailwindcss": "latest", 26 | "typescript": "latest" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/tests/test-configmap.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.testFramework.enabled }} 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: {{ include "grafana.fullname" . }}-test 6 | namespace: {{ include "grafana.namespace" . }} 7 | annotations: 8 | "helm.sh/hook": test-success 9 | "helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded" 10 | labels: 11 | {{- include "grafana.labels" . | nindent 4 }} 12 | data: 13 | run.sh: |- 14 | @test "Test Health" { 15 | url="http://{{ include "grafana.fullname" . }}/api/health" 16 | 17 | code=$(wget --server-response --spider --timeout 90 --tries 10 ${url} 2>&1 | awk '/^ HTTP/{print $2}') 18 | [ "$code" == "200" ] 19 | } 20 | {{- end }} 21 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/tests/test-role.yaml: -------------------------------------------------------------------------------- 1 | {{- if and (.Capabilities.APIVersions.Has "policy/v1beta1/PodSecurityPolicy") .Values.testFramework.enabled .Values.rbac.pspEnabled }} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: {{ include "grafana.fullname" . }}-test 6 | namespace: {{ include "grafana.namespace" . }} 7 | annotations: 8 | "helm.sh/hook": test-success 9 | "helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded" 10 | labels: 11 | {{- include "grafana.labels" . | nindent 4 }} 12 | rules: 13 | - apiGroups: ['policy'] 14 | resources: ['podsecuritypolicies'] 15 | verbs: ['use'] 16 | resourceNames: [{{ include "grafana.fullname" . }}-test] 17 | {{- end }} 18 | -------------------------------------------------------------------------------- /deployment/scripts/minikube/delete-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source ./kill-processes-by-keyword.sh "kubectl port-forward -n collie-server" 4 | 5 | source ./delete-agent.sh 6 | 7 | helm list -n collie-server -a | grep api-server > /dev/null 2>&1 8 | if [ $? -eq 0 ]; then 9 | helm uninstall -n collie-server api-server --wait 10 | fi 11 | helm list -n collie-server -a | grep grafana > /dev/null 2>&1 12 | if [ $? -eq 0 ]; then 13 | helm uninstall -n collie-server grafana --wait 14 | fi 15 | helm list -n collie-server -a | grep es > /dev/null 2>&1 16 | if [ $? -eq 0 ]; then 17 | helm uninstall -n collie-server es --wait 18 | fi 19 | 20 | kubectl get ns/collie-server > /dev/null 2>&1 21 | if [ $? -eq 0 ]; then 22 | kubectl delete namespace collie-server --wait 23 | fi 24 | 25 | 26 | -------------------------------------------------------------------------------- /deployment/helm-charts/api-server/templates/rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: Role 3 | metadata: 4 | name: {{ include "..fullname" . }} 5 | labels: 6 | {{- include "..labels" . | nindent 4 }} 7 | rules: 8 | - apiGroups: 9 | - "apps" 10 | resources: 11 | - deployments 12 | resourceNames: 13 | - api-server 14 | verbs: 15 | - patch 16 | 17 | --- 18 | apiVersion: rbac.authorization.k8s.io/v1 19 | kind: RoleBinding 20 | metadata: 21 | name: {{ include "..fullname" . }} 22 | labels: 23 | {{- include "..labels" . | nindent 4 }} 24 | 25 | roleRef: 26 | apiGroup: rbac.authorization.k8s.io 27 | kind: Role 28 | name: api-server 29 | subjects: 30 | - kind: ServiceAccount 31 | name: api-server 32 | namespace: collie-server 33 | 34 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/poddisruptionbudget.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.podDisruptionBudget }} 2 | apiVersion: {{ include "grafana.podDisruptionBudget.apiVersion" . }} 3 | kind: PodDisruptionBudget 4 | metadata: 5 | name: {{ include "grafana.fullname" . }} 6 | namespace: {{ include "grafana.namespace" . }} 7 | labels: 8 | {{- include "grafana.labels" . | nindent 4 }} 9 | {{- with .Values.labels }} 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | spec: 13 | {{- with .Values.podDisruptionBudget.minAvailable }} 14 | minAvailable: {{ . }} 15 | {{- end }} 16 | {{- with .Values.podDisruptionBudget.maxUnavailable }} 17 | maxUnavailable: {{ . }} 18 | {{- end }} 19 | selector: 20 | matchLabels: 21 | {{- include "grafana.selectorLabels" . | nindent 6 }} 22 | {{- end }} 23 | -------------------------------------------------------------------------------- /deployment/scripts/upgrade-minikube.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | minikube delete && \ 4 | sudo rm -rf /usr/local/bin/minikube && \ 5 | sudo curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 && \ 6 | sudo chmod +x minikube && \ 7 | sudo cp minikube /usr/local/bin/ && \ 8 | sudo rm minikube && \ 9 | minikube start &&\ 10 | 11 | # Enabling addons: ingress, dashboard 12 | minikube addons enable ingress && \ 13 | minikube addons enable dashboard && \ 14 | minikube addons enable metrics-server && \ 15 | # Showing enabled addons 16 | echo '\n\n\033[4;33m Enabled Addons \033[0m' && \ 17 | minikube addons list | grep STATUS && minikube addons list | grep enabled && \ 18 | 19 | # Showing current status of Minikube 20 | echo '\n\n\033[4;33m Current status of Minikube \033[0m' && minikube status -------------------------------------------------------------------------------- /api-server/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "-s -w" -o bin/collie-api-server-amd64 . 3 | docker rmi --force collie.azurecr.io/collie-api-server:1 4 | docker build -t collie.azurecr.io/collie-api-server:1 . 5 | 6 | swagger: 7 | go get -u github.com/swaggo/swag/cmd/swag 8 | swag init 9 | 10 | checkupdate: 11 | go list -u -m all 12 | update: 13 | go get -u 14 | generate: 15 | go generate ./... 16 | 17 | push: 18 | docker push collie.azurecr.io/collie-api-server:1 19 | 20 | deploy: 21 | cat ../helm-charts/api-server.yaml | envsubst | kubectl apply -f - 22 | 23 | SHELL := /bin/bash 24 | run: 25 | source ./.env && go run . 26 | 27 | test: 28 | go test ./... -race 29 | 30 | lint: 31 | go vet ./... 32 | gofmt -w -s . 33 | golangci-lint run 34 | 35 | release: build push 36 | -------------------------------------------------------------------------------- /api-server/model/error.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023-2024 Omnissa, LLC. 3 | SPDX-License-Identifier: Apache-2.0 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package model 17 | 18 | import "errors" 19 | 20 | var ( 21 | // ErrNoRow example 22 | ErrNoRow = errors.New("no rows in result set") 23 | ) 24 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/headless-service.yaml: -------------------------------------------------------------------------------- 1 | {{- $sts := list "sts" "StatefulSet" "statefulset" -}} 2 | {{- if or .Values.headlessService (and .Values.persistence.enabled (not .Values.persistence.existingClaim) (has .Values.persistence.type $sts)) }} 3 | apiVersion: v1 4 | kind: Service 5 | metadata: 6 | name: {{ include "grafana.fullname" . }}-headless 7 | namespace: {{ include "grafana.namespace" . }} 8 | labels: 9 | {{- include "grafana.labels" . | nindent 4 }} 10 | {{- with .Values.annotations }} 11 | annotations: 12 | {{- toYaml . | nindent 4 }} 13 | {{- end }} 14 | spec: 15 | clusterIP: None 16 | selector: 17 | {{- include "grafana.selectorLabels" . | nindent 4 }} 18 | type: ClusterIP 19 | ports: 20 | - name: {{ .Values.gossipPortName }}-tcp 21 | port: 9094 22 | {{- end }} 23 | -------------------------------------------------------------------------------- /api-server/model/admin.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023-2024 Omnissa, LLC. 3 | SPDX-License-Identifier: Apache-2.0 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package model 17 | 18 | // Admin example 19 | type Admin struct { 20 | ID int `json:"id" example:"1"` 21 | Name string `json:"name" example:"admin name"` 22 | } 23 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/rolebinding.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.create }} 2 | apiVersion: {{ include "grafana.rbac.apiVersion" . }} 3 | kind: RoleBinding 4 | metadata: 5 | name: {{ include "grafana.fullname" . }} 6 | namespace: {{ include "grafana.namespace" . }} 7 | labels: 8 | {{- include "grafana.labels" . | nindent 4 }} 9 | {{- with .Values.annotations }} 10 | annotations: 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | roleRef: 14 | apiGroup: rbac.authorization.k8s.io 15 | kind: Role 16 | {{- if .Values.rbac.useExistingRole }} 17 | name: {{ .Values.rbac.useExistingRole }} 18 | {{- else }} 19 | name: {{ include "grafana.fullname" . }} 20 | {{- end }} 21 | subjects: 22 | - kind: ServiceAccount 23 | name: {{ include "grafana.serviceAccountName" . }} 24 | namespace: {{ include "grafana.namespace" . }} 25 | {{- end }} 26 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/tests/test-rolebinding.yaml: -------------------------------------------------------------------------------- 1 | {{- if and (.Capabilities.APIVersions.Has "policy/v1beta1/PodSecurityPolicy") .Values.testFramework.enabled .Values.rbac.pspEnabled }} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: RoleBinding 4 | metadata: 5 | name: {{ include "grafana.fullname" . }}-test 6 | namespace: {{ include "grafana.namespace" . }} 7 | annotations: 8 | "helm.sh/hook": test-success 9 | "helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded" 10 | labels: 11 | {{- include "grafana.labels" . | nindent 4 }} 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: Role 15 | name: {{ include "grafana.fullname" . }}-test 16 | subjects: 17 | - kind: ServiceAccount 18 | name: {{ include "grafana.serviceAccountNameTest" . }} 19 | namespace: {{ include "grafana.namespace" . }} 20 | {{- end }} 21 | -------------------------------------------------------------------------------- /dashboard/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # You can override the included template(s) by including variable overrides 2 | # SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings 3 | # Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings 4 | # Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings 5 | # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings 6 | # Note that environment variables can be set in several places 7 | # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence 8 | stages: 9 | - test 10 | sast: 11 | stage: test 12 | include: 13 | - template: Security/SAST.gitlab-ci.yml 14 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/clusterrolebinding.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.rbac.create (or (not .Values.rbac.namespaced) .Values.rbac.extraClusterRoleRules) }} 2 | kind: ClusterRoleBinding 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | metadata: 5 | name: {{ include "grafana.fullname" . }}-clusterrolebinding 6 | labels: 7 | {{- include "grafana.labels" . | nindent 4 }} 8 | {{- with .Values.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | subjects: 13 | - kind: ServiceAccount 14 | name: {{ include "grafana.serviceAccountName" . }} 15 | namespace: {{ include "grafana.namespace" . }} 16 | roleRef: 17 | kind: ClusterRole 18 | {{- if .Values.rbac.useExistingRole }} 19 | name: {{ .Values.rbac.useExistingRole }} 20 | {{- else }} 21 | name: {{ include "grafana.fullname" . }}-clusterrole 22 | {{- end }} 23 | apiGroup: rbac.authorization.k8s.io 24 | {{- end }} 25 | -------------------------------------------------------------------------------- /deployment/scripts/deploy-api-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | B64CMD="base64" 4 | 5 | source .env 6 | 7 | # Delete deployments 8 | kubectl delete namespace collie-server collie-agent --wait=true 9 | 10 | # redeploy 11 | export ES_KEY_B64=$(echo -n $ES_KEY | $B64CMD) 12 | export OAUTH_CSP_CLIENTID_B64=$(echo -n $OAUTH_CSP_CLIENTID | $B64CMD) 13 | export OAUTH_CSP_CLIENTSECRET_B64=$(echo -n $OAUTH_CSP_CLIENTSECRET | $B64CMD) 14 | export OAUTH_GITLAB_CLIENTID_B64=$(echo -n $OAUTH_GITLAB_CLIENTID | $B64CMD) 15 | export OAUTH_GITLAB_CLIENTSECRET_B64=$(echo -n $OAUTH_GITLAB_CLIENTSECRET | $B64CMD) 16 | 17 | envsubst < api-server.yaml | kubectl apply -f - 18 | kubectl wait deployment -n collie-server api-server --for condition=Available=True --timeout=90s 19 | AUTH_TOKEN=gitlab/$(source auth-gitlab.sh | jq -r '.access_token') 20 | sleep 10 21 | curl -skH "Authorization: $AUTH_TOKEN" https://collie.eng.omnissa.com/collie/api/v1/onboarding/bootstrap 22 | 23 | -------------------------------------------------------------------------------- /k8s-agent/internal/config/version.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023-2024 Omnissa, LLC. 3 | SPDX-License-Identifier: Apache-2.0 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package config 17 | 18 | import "fmt" 19 | 20 | type AgentVersion struct { 21 | GitCommit, GitRef, Version string 22 | } 23 | 24 | func (a *AgentVersion) String() string { 25 | return fmt.Sprintf("GitCommit=%q GitRef=%q Version=%q", a.GitCommit, a.GitRef, a.Version) 26 | } 27 | -------------------------------------------------------------------------------- /deployment/scripts/minikube/debug-image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | NAMESPACE=collie-server 4 | TARGET_CONFIG=pod/elasticsearch-master-0 5 | 6 | kubectl -n $NAMESPACE get $TARGET_CONFIG -ojson | jq . > pod-debug1.json 7 | 8 | #jq "del(.spec.containers[0].readinessProbe)" pod-debug.json > pod-debug.json 9 | jq "del(.spec.initContainers[0].command)" pod-debug1.json > pod-debug2.json 10 | jq "del(.spec.initContainers[0].args)" pod-debug2.json > pod-debug3.json 11 | jq '.spec.initContainers[0] += {command: ["/bin/bash"], args: ["-c", "while true; do sleep 30; done;"]}' pod-debug3.json > pod-debug4.json 12 | jq '.metadata.name = "debug-image"' pod-debug4.json > pod-debug5.json 13 | 14 | cat pod-debug5.json | yq -P > pod-debug.yml 15 | 16 | kubectl -n $NAMESPACE delete pod debug-image 17 | sleep 20 18 | kubectl -n $NAMESPACE apply -f pod-debug.yml 19 | sleep 20 20 | kubectl -n $NAMESPACE exec -it debug-image -- /bin/bash 21 | 22 | # rm -f pod-debug*.json 23 | # rm -f pod-debug*.yml 24 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/tests/test-podsecuritypolicy.yaml: -------------------------------------------------------------------------------- 1 | {{- if and (.Capabilities.APIVersions.Has "policy/v1beta1/PodSecurityPolicy") .Values.testFramework.enabled .Values.rbac.pspEnabled }} 2 | apiVersion: policy/v1beta1 3 | kind: PodSecurityPolicy 4 | metadata: 5 | name: {{ include "grafana.fullname" . }}-test 6 | annotations: 7 | "helm.sh/hook": test-success 8 | "helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded" 9 | labels: 10 | {{- include "grafana.labels" . | nindent 4 }} 11 | spec: 12 | allowPrivilegeEscalation: true 13 | privileged: false 14 | hostNetwork: false 15 | hostIPC: false 16 | hostPID: false 17 | fsGroup: 18 | rule: RunAsAny 19 | seLinux: 20 | rule: RunAsAny 21 | supplementalGroups: 22 | rule: RunAsAny 23 | runAsUser: 24 | rule: RunAsAny 25 | volumes: 26 | - configMap 27 | - downwardAPI 28 | - emptyDir 29 | - projected 30 | - csi 31 | - secret 32 | {{- end }} 33 | -------------------------------------------------------------------------------- /api-server/controller/controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023-2024 Omnissa, LLC. 3 | SPDX-License-Identifier: Apache-2.0 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package controller 17 | 18 | // Controller example 19 | type Controller struct { 20 | } 21 | 22 | // NewController example 23 | func NewController() *Controller { 24 | return &Controller{} 25 | } 26 | 27 | // Message example 28 | type Message struct { 29 | Message string `json:"message" example:"message"` 30 | } 31 | -------------------------------------------------------------------------------- /dashboard/collie/grafana-export-dashboards.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="grafana-dashboards" 4 | 5 | # Iterate through dashboards using the current API Key 6 | for dashboard_uid in $(curl -sS -u $COLLIE_GRAFANA_CREDENTIAL $COLLIE_GRAFANA_URL/api/search\?query\=\& | jq -r '.[] | select( .type | contains("dash-db")) | .uid'); do 7 | url=$(echo $COLLIE_GRAFANA_URL/api/dashboards/uid/$dashboard_uid | tr -d '\r') 8 | dashboard_json=$(curl -sS -u $COLLIE_GRAFANA_CREDENTIAL $url) 9 | dashboard_title=$(echo $dashboard_json | jq -r '.dashboard | .title' | sed -r 's/[ \/]+/_/g') 10 | dashboard_version=$(echo $dashboard_json | jq -r '.dashboard | .version') 11 | folder_title="$(echo $dashboard_json | jq -r '.meta | .folderTitle')" 12 | 13 | echo "Creating: ${DIR}/${folder_title}/${dashboard_title}_v${dashboard_version}.json" 14 | mkdir -p "${DIR}/${folder_title}" 15 | echo ${dashboard_json} | jq -r {meta:.meta}+.dashboard > "${DIR}/${folder_title}/${dashboard_title}_v${dashboard_version}.json" 16 | done -------------------------------------------------------------------------------- /deployment/scripts/auth-gitlab.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source .env 4 | 5 | GITLAB_URL="https://gitlab.eng.omnissa.com" 6 | SCOPES="read_user" 7 | 8 | # Request an OAuth access token from GitLab 9 | response=$(curl --silent --request POST \ 10 | --url "$GITLAB_URL/oauth/token" \ 11 | --header 'content-type: application/x-www-form-urlencoded' \ 12 | --data "client_id=$OAUTH_GITLAB_CLIENTID&client_secret=$OAUTH_GITLAB_CLIENTSECRET&grant_type=client_credentials&scope=$SCOPES" 13 | ) 14 | 15 | echo $response 16 | 17 | # Extract the access token from the response using jq 18 | # ACCESS_TOKEN=$(echo "$response" | jq -r '.access_token') 19 | # echo Access token: $ACCESS_TOKEN 20 | # echo Token info: $(curl -s $GITLAB_URL/oauth/token/info?access_token=$ACCESS_TOKEN) 21 | 22 | # USER_RESPONSE=$(curl -s $GITLAB_URL/oauth/userinfo?access_token=$ACCESS_TOKEN) 23 | # echo OAuth user info: $USER_RESPONSE 24 | 25 | # USER_RESPONSE=$(curl -s $GITLAB_URL/api/v4/user?access_token=$ACCESS_TOKEN) 26 | # echo Gitlab info: $USER_RESPONSE 27 | 28 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: grafana 3 | version: 6.57.1 4 | appVersion: 9.5.3 5 | kubeVersion: "^1.8.0-0" 6 | description: The leading tool for querying and visualizing time series and metrics. 7 | home: https://grafana.net 8 | icon: https://raw.githubusercontent.com/grafana/grafana/master/public/img/logo_transparent_400x.png 9 | sources: 10 | - https://github.com/grafana/grafana 11 | - https://github.com/grafana/helm-charts 12 | annotations: 13 | "artifacthub.io/links": | 14 | - name: Chart Source 15 | url: https://github.com/grafana/helm-charts 16 | - name: Upstream Project 17 | url: https://github.com/grafana/grafana 18 | maintainers: 19 | - name: zanhsieh 20 | email: zanhsieh@gmail.com 21 | - name: rtluckie 22 | email: rluckie@cisco.com 23 | - name: maorfr 24 | email: maor.friedman@redhat.com 25 | - name: Xtigyro 26 | email: miroslav.hadzhiev@gmail.com 27 | - name: torstenwalter 28 | email: mail@torstenwalter.de 29 | engine: gotpl 30 | type: application -------------------------------------------------------------------------------- /deployment/scripts/bad-pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | annotations: 5 | deployment.kubernetes.io/revision: "1" 6 | labels: 7 | app.kubernetes.io/instance: helloworld-web 8 | app.kubernetes.io/name: helloworld-web 9 | app.kubernetes.io/version: v0.42.1 10 | name: helloworld-web 11 | namespace: default 12 | spec: 13 | replicas: 1 14 | revisionHistoryLimit: 10 15 | selector: 16 | matchLabels: 17 | app.kubernetes.io/instance: helloworld-web 18 | app.kubernetes.io/name: helloworld-web 19 | template: 20 | metadata: 21 | labels: 22 | app.kubernetes.io/instance: helloworld-web 23 | app.kubernetes.io/name: helloworld-web 24 | spec: 25 | containers: 26 | - env: 27 | - name: PROVIDER 28 | value: aks 29 | image: crccheck/hello-world 30 | imagePullPolicy: IfNotPresent 31 | name: helloworld-web 32 | ports: 33 | - containerPort: 80 34 | hostPort: 8080 35 | hostIP: 10.0.0.1 -------------------------------------------------------------------------------- /deployment/helm-charts/api-server/templates/hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.autoscaling.enabled }} 2 | apiVersion: autoscaling/v2beta1 3 | kind: HorizontalPodAutoscaler 4 | metadata: 5 | name: {{ include "..fullname" . }} 6 | labels: 7 | {{- include "..labels" . | nindent 4 }} 8 | spec: 9 | scaleTargetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: {{ include "..fullname" . }} 13 | minReplicas: {{ .Values.autoscaling.minReplicas }} 14 | maxReplicas: {{ .Values.autoscaling.maxReplicas }} 15 | metrics: 16 | {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} 17 | - type: Resource 18 | resource: 19 | name: cpu 20 | targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} 21 | {{- end }} 22 | {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} 23 | - type: Resource 24 | resource: 25 | name: memory 26 | targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} 27 | {{- end }} 28 | {{- end }} 29 | -------------------------------------------------------------------------------- /k8s-agent/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # This file is a template, and might need editing before it works on your project. 2 | image: grpc/go 3 | 4 | before_script: 5 | - export REPO_NAME=`echo $CI_PROJECT_URL|sed 's/.*:\/\///g;'` 6 | - mkdir -p $GOPATH/src/$(dirname $REPO_NAME) 7 | - ln -svf $CI_PROJECT_DIR $GOPATH/src/$REPO_NAME 8 | - cd $GOPATH/src/$REPO_NAME 9 | - go get 10 | 11 | stages: 12 | - test 13 | - build 14 | 15 | format: 16 | stage: test 17 | script: 18 | - go fmt $(go list ./... | grep -v /vendor/) 19 | - go vet $(go list ./... | grep -v /vendor/) 20 | 21 | test: 22 | stage: test 23 | script: 24 | - go test -race $(go list ./... | grep -v /vendor/) 25 | 26 | compile: 27 | stage: build 28 | script: 29 | - go get github.com/micro/protoc-gen-micro 30 | - protoc --proto_path=$GOPATH:. --micro_out=. --go_out=. $GOPATH/src/$REPO_NAME/proto/greeter.proto 31 | - go build -race -ldflags "-extldflags '-static'" -o $CI_PROJECT_DIR/service 32 | artifacts: 33 | paths: 34 | - service 35 | -------------------------------------------------------------------------------- /api-server/httputil/error.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023-2024 Omnissa, LLC. 3 | SPDX-License-Identifier: Apache-2.0 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package httputil 17 | 18 | import "github.com/gin-gonic/gin" 19 | 20 | func Abort(ctx *gin.Context, code int, err error) { 21 | er := HTTPError{ 22 | Code: code, 23 | Message: err.Error(), 24 | } 25 | ctx.AbortWithStatusJSON(code, er) 26 | } 27 | 28 | type HTTPError struct { 29 | Code int `json:"code" example:"400"` 30 | Message string `json:"message" example:"status bad request"` 31 | } 32 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/dashboards-json-configmap.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.dashboards }} 2 | {{ $files := .Files }} 3 | {{- range $provider, $dashboards := .Values.dashboards }} 4 | apiVersion: v1 5 | kind: ConfigMap 6 | metadata: 7 | name: {{ include "grafana.fullname" $ }}-dashboards-{{ $provider }} 8 | namespace: {{ include "grafana.namespace" $ }} 9 | labels: 10 | {{- include "grafana.labels" $ | nindent 4 }} 11 | dashboard-provider: {{ $provider }} 12 | {{- if $dashboards }} 13 | data: 14 | {{- $dashboardFound := false }} 15 | {{- range $key, $value := $dashboards }} 16 | {{- if (or (hasKey $value "json") (hasKey $value "file")) }} 17 | {{- $dashboardFound = true }} 18 | {{- print $key | nindent 2 }}.json: 19 | {{- if hasKey $value "json" }} 20 | |- 21 | {{- $value.json | nindent 6 }} 22 | {{- end }} 23 | {{- if hasKey $value "file" }} 24 | {{- toYaml ( $files.Get $value.file ) | nindent 4}} 25 | {{- end }} 26 | {{- end }} 27 | {{- end }} 28 | {{- if not $dashboardFound }} 29 | {} 30 | {{- end }} 31 | {{- end }} 32 | --- 33 | {{- end }} 34 | 35 | {{- end }} 36 | -------------------------------------------------------------------------------- /dashboard/cert/gen-self-signed.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -f self-signed-root.key 4 | rm -f self-signed-root.crt 5 | rm -f self-signed-root.srl 6 | 7 | # Generate self-signed root CA 8 | openssl req -config req-self-signed-root.conf -newkey rsa:2048 -x509 -days 3650 -nodes -sha256 -out self-signed-root.crt -keyout self-signed-root.key 9 | 10 | #openssl x509 -text -noout -purpose -ocsp_uri -in self-signed-root.crt 11 | 12 | # Verify 13 | #openssl rsa -check -noout -in self-signed-root.key 14 | #openssl x509 -noout -modulus -in self-signed-root.crt | openssl md5 15 | #openssl rsa -noout -modulus -in self-signed-root.key | openssl md5 16 | 17 | # Sign nanw 18 | openssl x509 -req -days 3650 -sha256 -CA self-signed-root.crt -CAkey self-signed-root.key -CAcreateserial -in nanw.csr -extfile v3_ext.txt -out nanw.crt 19 | 20 | # Verify 21 | #openssl rsa -check -noout -in nanw.key 22 | #openssl x509 -noout -modulus -in nanw.crt | openssl md5 23 | #openssl rsa -noout -modulus -in nanw.key | openssl md5 24 | 25 | #openssl verify -verbose -CAfile self-signed-root.crt nanw.crt 26 | 27 | openssl x509 -text -noout -purpose -ocsp_uri -in nanw.crt 28 | -------------------------------------------------------------------------------- /api-server/config/oauth-default.yaml: -------------------------------------------------------------------------------- 1 | oauth: 2 | hostUrl: http://localhost:8080 3 | csp: 4 | host: https://console-stg.cloud.omnissa.com 5 | authUrl: ${oauth.csp.host}/csp/gateway/discovery 6 | tokenUrl: ${oauth.csp.host}/csp/gateway/am/api/auth/token 7 | jwksUrl: ${oauth.csp.host}/csp/gateway/am/api/auth/token-public-key?format=jwks 8 | redirectUrl: ${collie.url}/collie/oauth/callback/csp 9 | #clientId: 10 | #clientSecret: 11 | orgId: e7923078-6663-4178-9555-bcd5a036693e 12 | issuer: https://gaz-preview.csp-vidm-prod.com 13 | gitlab: 14 | host: https://gitlab.eng.omnissa.com 15 | authUrl: ${oauth.gitlab.host}/oauth/authorize 16 | tokenUrl: ${oauth.gitlab.host}/oauth/token 17 | redirectUrl: ${collie.url}/collie/oauth/callback/gitlab 18 | #clientId: 19 | #clientSecret: 20 | google: 21 | authUrl: https://accounts.google.com/o/oauth2/auth 22 | tokenUrl: https://accounts.google.com/o/oauth2/token 23 | redirectUrl: ${collie.url}/collie/oauth/callback/google 24 | #clientId: 25 | #clientSecret: 26 | -------------------------------------------------------------------------------- /k8s-agent/internal/reporter/collie_client_interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023-2024 Omnissa, LLC. 3 | SPDX-License-Identifier: Apache-2.0 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package reporter 17 | 18 | import ( 19 | "collie-agent/internal/model" 20 | ) 21 | 22 | type ICollieClient interface { 23 | Info() error 24 | ReportClusterInfo(info model.ClusterInfo) 25 | ReportResource(data interface{}) 26 | ReportActivity(operation string, resource string) 27 | ReportError(operation string, resource string, e error) 28 | ReportCompliance(data *model.Compliance) 29 | ReportBulk(docs []*any) 30 | ReportCompletion() 31 | } 32 | -------------------------------------------------------------------------------- /deployment/scripts/minikube/deploy-grafana.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | helm list -n collie-server -a | grep grafana > /dev/null 2>&1 4 | if [ $? -eq 0 ]; then 5 | helm uninstall -n collie-server grafana --wait 6 | fi 7 | 8 | 9 | export ES_PASSWORD=$(kubectl get -n collie-server secret/elasticsearch-master-credentials -o jsonpath="{.data.password}" | base64 -d) 10 | 11 | envsubst < ./grafana/values-custom.yaml > ./grafana/tmp.yaml 12 | helm install -n collie-server grafana ./grafana -f ./grafana/values.yaml -f ./grafana/tmp.yaml --wait 13 | rm ./grafana/tmp.yaml 14 | 15 | sleep 10 16 | 17 | source ./kill-processes-by-keyword.sh "kubectl port-forward -n collie-server --address 0.0.0.0 services/grafana" 18 | kubectl port-forward -n collie-server --address 0.0.0.0 services/grafana 3000:3000 & 19 | 20 | sleep 5 21 | 22 | GRAFANA_PWD=$(kubectl get secret --namespace collie-server grafana -o jsonpath="{.data.admin-password}" | base64 --decode) 23 | 24 | 25 | curl -X POST \ 26 | -H "Content-Type: application/json" \ 27 | -u "admin:$GRAFANA_PWD" \ 28 | http://collie-dev.org:3000/api/dashboards/db?orgId=1 \ 29 | -d @./grafana/dashboards/workaround-helm.json 30 | 31 | 32 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/clusterrole.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.rbac.create (or (not .Values.rbac.namespaced) .Values.rbac.extraClusterRoleRules) (not .Values.rbac.useExistingRole) }} 2 | kind: ClusterRole 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | metadata: 5 | labels: 6 | {{- include "grafana.labels" . | nindent 4 }} 7 | {{- with .Values.annotations }} 8 | annotations: 9 | {{- toYaml . | nindent 4 }} 10 | {{- end }} 11 | name: {{ include "grafana.fullname" . }}-clusterrole 12 | {{- if or .Values.sidecar.dashboards.enabled .Values.rbac.extraClusterRoleRules .Values.sidecar.datasources.enabled .Values.sidecar.plugins.enabled .Values.sidecar.alerts.enabled }} 13 | rules: 14 | {{- if or .Values.sidecar.dashboards.enabled .Values.sidecar.datasources.enabled .Values.sidecar.plugins.enabled .Values.sidecar.alerts.enabled }} 15 | - apiGroups: [""] # "" indicates the core API group 16 | resources: ["configmaps", "secrets"] 17 | verbs: ["get", "watch", "list"] 18 | {{- end}} 19 | {{- with .Values.rbac.extraClusterRoleRules }} 20 | {{- toYaml . | nindent 2 }} 21 | {{- end}} 22 | {{- else }} 23 | rules: [] 24 | {{- end}} 25 | {{- end}} 26 | -------------------------------------------------------------------------------- /deployment/scripts/azure/deploy-grafana.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | helm list -n collie-server -a | grep grafana > /dev/null 2>&1 4 | if [ $? -eq 0 ]; then 5 | helm uninstall -n collie-server grafana --wait 6 | fi 7 | 8 | 9 | export ES_PASSWORD=$(kubectl get -n collie-server secret/elasticsearch-master-credentials -o jsonpath="{.data.password}" | base64 -d) 10 | 11 | cd ../../helm-charts 12 | envsubst < ./grafana/values-custom.yaml > ./grafana/tmp.yaml 13 | helm install -n collie-server grafana ./grafana -f ./grafana/values.yaml -f ./grafana/tmp.yaml --wait 14 | rm ./grafana/tmp.yaml 15 | 16 | sleep 10 17 | 18 | #source ./kill-processes-by-keyword.sh "kubectl port-forward -n collie-server --address 0.0.0.0 services/grafana" 19 | #kubectl port-forward -n collie-server --address 0.0.0.0 services/grafana 3000:3000 & 20 | 21 | sleep 5 22 | 23 | GRAFANA_PWD=$(kubectl get secret --namespace collie-server grafana -o jsonpath="{.data.admin-password}" | base64 --decode) 24 | 25 | 26 | curl -X POST \ 27 | -H "Content-Type: application/json" \ 28 | -u "admin:$GRAFANA_PWD" \ 29 | http://collie-dev.org:3000/api/dashboards/db?orgId=1 \ 30 | -d @./grafana/dashboards/workaround-helm.json 31 | 32 | 33 | -------------------------------------------------------------------------------- /api-server/service/template/clustervpa-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: agent-autoscaler 5 | namespace: collie-agent 6 | labels: 7 | app.kubernetes.io/name: agent 8 | app.kubernetes.io/instance: agent 9 | app.kubernetes.io/version: "v1" 10 | app.kubernetes.io/managed-by: collie 11 | 12 | data: 13 | # Increase memory requests/limits by 256Mi for every 20 nodes. round_up(nodes/nodes_per_step)*step 14 | # For example, for 150 nodes: round_up(150/20)*256Mi=2048Mi 15 | 16 | # in case of large nodes, cores-per-step will ensure that we continue to scale the agent 17 | agent-autoscaler: |- 18 | { 19 | "agent": { 20 | "requests": { 21 | "memory": { 22 | "base": "0", 23 | "max": "8Gi", 24 | "step": "256Mi", 25 | "nodesPerStep": 20, 26 | "coresPerStep": 480 27 | } 28 | }, 29 | "limits": { 30 | "memory": { 31 | "base": "0", 32 | "max": "8Gi", 33 | "step": "256Mi", 34 | "nodesPerStep": 20, 35 | "coresPerStep": 480 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/image-renderer-service.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.imageRenderer.enabled .Values.imageRenderer.service.enabled }} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ include "grafana.fullname" . }}-image-renderer 6 | namespace: {{ include "grafana.namespace" . }} 7 | labels: 8 | {{- include "grafana.imageRenderer.labels" . | nindent 4 }} 9 | {{- with .Values.imageRenderer.service.labels }} 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- with .Values.imageRenderer.service.annotations }} 13 | annotations: 14 | {{- toYaml . | nindent 4 }} 15 | {{- end }} 16 | spec: 17 | type: ClusterIP 18 | {{- with .Values.imageRenderer.service.clusterIP }} 19 | clusterIP: {{ . }} 20 | {{- end }} 21 | ports: 22 | - name: {{ .Values.imageRenderer.service.portName }} 23 | port: {{ .Values.imageRenderer.service.port }} 24 | protocol: TCP 25 | targetPort: {{ .Values.imageRenderer.service.targetPort }} 26 | {{- with .Values.imageRenderer.appProtocol }} 27 | appProtocol: {{ . }} 28 | {{- end }} 29 | selector: 30 | {{- include "grafana.imageRenderer.selectorLabels" . | nindent 4 }} 31 | {{- end }} 32 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/pvc.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) (eq .Values.persistence.type "pvc")}} 2 | apiVersion: v1 3 | kind: PersistentVolumeClaim 4 | metadata: 5 | name: {{ include "grafana.fullname" . }} 6 | namespace: {{ include "grafana.namespace" . }} 7 | labels: 8 | {{- include "grafana.labels" . | nindent 4 }} 9 | {{- with .Values.persistence.extraPvcLabels }} 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- with .Values.persistence.annotations }} 13 | annotations: 14 | {{- toYaml . | nindent 4 }} 15 | {{- end }} 16 | {{- with .Values.persistence.finalizers }} 17 | finalizers: 18 | {{- toYaml . | nindent 4 }} 19 | {{- end }} 20 | spec: 21 | accessModes: 22 | {{- range .Values.persistence.accessModes }} 23 | - {{ . | quote }} 24 | {{- end }} 25 | resources: 26 | requests: 27 | storage: {{ .Values.persistence.size | quote }} 28 | {{- with .Values.persistence.storageClassName }} 29 | storageClassName: {{ . }} 30 | {{- end }} 31 | {{- with .Values.persistence.selectorLabels }} 32 | selector: 33 | matchLabels: 34 | {{- toYaml . | nindent 6 }} 35 | {{- end }} 36 | {{- end }} 37 | -------------------------------------------------------------------------------- /deployment/scripts/README.md: -------------------------------------------------------------------------------- 1 | # Tutorial 2 | 3 | ### Onboard new K8S cluster, with Collie.eng.omnissa.com 4 | 5 | Get bootstrap command 6 | ``` 7 | curl -skH "Authorization: bc3dcc07f8263dda643d1703cd5254f2" https://collie.eng.omnissa.com/collie/api/v1/onboarding/bootstrap-cmd 8 | ``` 9 | 10 | It will generate a command line like below. Make sure your current kubectl points to the taregt context, then execute the command. 11 | 12 | ``` 13 | curl -skH "Authorization: Token bc3dcc07f8263dda643d1703cd5254f2" "https://collie.eng.omnissa.com/collie/api/v1/onboarding/agent.yaml?provider=AKS" | kubectl apply -f - 14 | ``` 15 | 16 | Navigate to dashboard 17 | ``` 18 | https://collie.eng.omnissa.com/d/qIbLYbT4z/k8s-compliance-report?orgId=1 19 | ``` 20 | 21 | ### Start local dev server (not using collie.eng.omnissa.com) 22 | ``` 23 | cd collie/api-server 24 | cp collie/helm-charts/.env.example .env 25 | vi .env 26 | source .env 27 | go run . 28 | ``` 29 | 30 | ### Deploy local cluster 31 | ``` 32 | cd collie/helm-charts 33 | cp .env.example .env 34 | vi .env 35 | source .env 36 | envsubst < api-server.yaml | kubectl apply -f - 37 | ``` 38 | 39 | ####Onboard agent 40 | 41 | ``` 42 | curl http://localhost:8080/api/v1/onboarding/bootstrap-cmd 43 | ``` 44 | -------------------------------------------------------------------------------- /deployment/helm-charts/Makefile: -------------------------------------------------------------------------------- 1 | default: test 2 | 3 | .ONESHELL: 4 | 5 | .PHONY: help 6 | help: ## Display this help 7 | @awk 'BEGIN {FS = ":.*##"; printf "Usage: make \033[36m\033[0m\n\nTargets:\n"} /^[a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-10s\033[0m %s\n", $$1, $$2 }' $(MAKEFILE_LIST) 8 | 9 | .PHONY: build 10 | build: ## Build helm-tester docker image 11 | cd ../helpers/helm-tester && \ 12 | for i in {1..5}; do docker build -t helm-tester . && break || sleep 15; done 13 | 14 | .PHONY: deps 15 | deps: ## Update helm charts dependencies 16 | helm dependency update 17 | 18 | .PHONY: lint 19 | lint: ## Lint helm templates 20 | helm lint --strict ./ 21 | 22 | .PHONY: lint-python 23 | lint-python: ## Lint python scripts 24 | black --diff --check --exclude='ve/|venv/' . 25 | 26 | .PHONY: pytest 27 | pytest: ## Run python tests 28 | pytest -sv --color=yes 29 | 30 | .PHONY: template 31 | template: ## Render chart templates 32 | helm template ./ 33 | 34 | .PHONY: test 35 | test: ## Run all tests in a docker container 36 | docker run --rm -i --user "$$(id -u):$$(id -g)" -v $$(pwd)/../:/app -w /app/$$(basename $$(pwd)) helm-tester make test-all 37 | 38 | .PHONY: test-all ## Run all tests 39 | test-all: deps lint template pytest 40 | 41 | 42 | -------------------------------------------------------------------------------- /deployment/helm-charts/api-server/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: api-server 3 | description: A Helm chart for Kubernetes 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.1.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "1.16.0" 25 | -------------------------------------------------------------------------------- /dashboard/cert/self-signed-root.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDPDCCAiQCCQDiYx7I1uzREzANBgkqhkiG9w0BAQsFADBgMQswCQYDVQQGEwJV 3 | UzELMAkGA1UECAwCQ0ExEjAQBgNVBAcMCVBhbG8gQWx0bzEPMA0GA1UECgwGVk13 4 | YXJlMQwwCgYDVQQLDANFVUMxETAPBgNVBAMMCG5hbnctZGV2MB4XDTIyMDkxMzIx 5 | MTg1MloXDTMyMDkxMDIxMTg1MlowYDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNB 6 | MRIwEAYDVQQHDAlQYWxvIEFsdG8xDzANBgNVBAoMBlZNd2FyZTEMMAoGA1UECwwD 7 | RVVDMREwDwYDVQQDDAhuYW53LWRldjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC 8 | AQoCggEBANYo30Xy4VtLErRznTlwF6mB5D6M0t5WYqGYPsN9ApPBrOWXxP3JkhYW 9 | LiNHX1wwUSTlyE9pY4jamkQSiKTQr5Op8ahW/cftp1pDJOCwWg1RKxyM29+l3iSt 10 | zMjS3IZxcbwwg+wAqJ9uJ+UT6EWzUCYLhY9E9/kGGcf0kocy0KcPKIX6HKg3q1LQ 11 | HCvFlf/D+Z5uW+m0sFxEqKOTO0asDV0Rs956Gi9/rdlwVnncIdabXEK5lPM92NaM 12 | N4hvfPpBGPthxPF7OvCPNfWm/5hlBRjHS7qVYOZSsZ/D6tWMaFTv9dLBUwPW0c5k 13 | l/Uiy80/63j9+WCQ/rkQzYUexT8xwIECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEA 14 | yEsppXNxrmX/LzuStWWaNxI2JRScknFQ5pu3Ny/OL9E2pWo32CpgqJH5X8casNv8 15 | +v0gJGoIEfVjXJFB8mCepLuT5ZW2sXCnqPFtovTZNLdb3kdF9Qggf99y5HNZQqJb 16 | bGnP1o+K18MxNTF4GBsV3BM7w4qov6uNPJhIcVP+SjkD/QuQyC0hDNNjhQ1+X8JC 17 | +Ap1zYJhf64VWRk4qAw5+KP1q5pNL0ny2eU2J7a0idRPnHqA0pgtNj9kgGx0e/Gr 18 | ReOAg77lbkqRtDYn3SHfgY6mS58hcs9TKkhxsry6d9HjHiJaHwpPtvDVTAE8xfgK 19 | MATqmYuWokzNsUjG0N37Yw== 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/secret.yaml: -------------------------------------------------------------------------------- 1 | {{- if or (and (not .Values.admin.existingSecret) (not .Values.env.GF_SECURITY_ADMIN_PASSWORD__FILE) (not .Values.env.GF_SECURITY_ADMIN_PASSWORD) (not .Values.env.GF_SECURITY_DISABLE_INITIAL_ADMIN_CREATION)) (and .Values.ldap.enabled (not .Values.ldap.existingSecret)) }} 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: {{ include "grafana.fullname" . }} 6 | namespace: {{ include "grafana.namespace" . }} 7 | labels: 8 | {{- include "grafana.labels" . | nindent 4 }} 9 | {{- with .Values.annotations }} 10 | annotations: 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | type: Opaque 14 | data: 15 | {{- if and (not .Values.env.GF_SECURITY_DISABLE_INITIAL_ADMIN_CREATION) (not .Values.admin.existingSecret) (not .Values.env.GF_SECURITY_ADMIN_PASSWORD__FILE) (not .Values.env.GF_SECURITY_ADMIN_PASSWORD) }} 16 | admin-user: {{ .Values.adminUser | b64enc | quote }} 17 | {{- if .Values.adminPassword }} 18 | admin-password: {{ .Values.adminPassword | b64enc | quote }} 19 | {{- else }} 20 | admin-password: {{ include "grafana.password" . }} 21 | {{- end }} 22 | {{- end }} 23 | {{- if not .Values.ldap.existingSecret }} 24 | ldap-toml: {{ tpl .Values.ldap.config $ | b64enc | quote }} 25 | {{- end }} 26 | {{- end }} 27 | -------------------------------------------------------------------------------- /dashboard/cert/nanw.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIDUTCCAjkCAQAwazELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQH 3 | DAlQYWxvIEFsdG8xDzANBgNVBAoMBlZNd2FyZTEMMAoGA1UECwwDRVVDMRwwGgYD 4 | VQQDDBNuYW53LmVuZy52bXdhcmUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 5 | MIIBCgKCAQEArquASj3cEjfEGl5t7lQBAN8ltGyN/k+eox84HFpegvfzHPhnNoNJ 6 | TIptTTtEChmLOh8aWiZFMCgglS9Qf5OPCBmbtu9Ng7YzkKfj73Oio7iPngaS9V3O 7 | mVq1ujp4fB2jOAx5I0WlrZS1hW22l+bziMsTXAT150mx5nyPGHN0i0CCwlqjxJwL 8 | ttf1fqlYvtCorntdSCJK4I9IKCdNLjxHovyrzjLqwKLww0mRb0HuALnhu49EieVK 9 | rBTLr1weEyrsaleQKtnOJkZAuazJJsdt4w9hSbyCJ3extCyT6urIkl0wKMNvpIHx 10 | KCiKrhrDqfucXTTKBJWfT7WdqVTAQdX9JwIDAQABoIGgMIGdBgkqhkiG9w0BCQ4x 11 | gY8wgYwwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUCXCTin5bwu+zegZyBBv1lGsB 12 | bQ4wDgYDVR0PAQH/BAQDAgP4MBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMBMDUGA1Ud 13 | EQQuMCyCE25hbncuZW5nLnZtd2FyZS5jb22CFWNvbGxpZS5lbmcudm13YXJlLmNv 14 | bTANBgkqhkiG9w0BAQsFAAOCAQEALRqXk3mgXYmhNJTi/XC28neqydwV1aq5C66T 15 | /3iYUYwOUbaAjS8xbLaAv2VSWbRj44NwRuYdVZmKznpetVe7km+mZeQ0jdZl4GhO 16 | r3Q+cA66c6zg/1xQ0/d3pcmIpWIynj2+HCYLVpChpf0EGyVBCBYTD4BBx5lf4zZC 17 | L4QgLmLCDgr9i6W2ERqGC4/+FmlGSN3PCN0OMR7cVuFK56O7Bj++4IeFwzFvHQo3 18 | 4dsJD07zGsgFqq5kKA1PSbcuW7kDfALJUmeDX1UCvIePT5ql6CS99rEHU8Pkctbg 19 | Ytk6YvAgWptP3VP1Ca1NZWdZxfzEzW6ZVZFGrbO93BMwvhXV2Q== 20 | -----END CERTIFICATE REQUEST----- 21 | -------------------------------------------------------------------------------- /k8s-agent/internal/services/version/version.go: -------------------------------------------------------------------------------- 1 | //go:generate mockgen -destination ./mock/version.go . Interface 2 | package version 3 | 4 | import ( 5 | "fmt" 6 | "regexp" 7 | "strconv" 8 | 9 | "github.com/sirupsen/logrus" 10 | "k8s.io/apimachinery/pkg/version" 11 | "k8s.io/client-go/kubernetes" 12 | ) 13 | 14 | type Interface interface { 15 | Full() string 16 | MinorInt() int 17 | } 18 | 19 | func Get(log logrus.FieldLogger, clientset kubernetes.Interface) (Interface, error) { 20 | cs, ok := clientset.(*kubernetes.Clientset) 21 | if !ok { 22 | return nil, fmt.Errorf("expected clientset to be of type *kubernetes.Clientset but was %T", clientset) 23 | } 24 | 25 | sv, err := cs.ServerVersion() 26 | if err != nil { 27 | return nil, fmt.Errorf("getting server version: %w", err) 28 | } 29 | 30 | log.Infof("kubernetes version %s.%s", sv.Major, sv.Minor) 31 | 32 | m, err := strconv.Atoi(regexp.MustCompile(`^(\d+)`).FindString(sv.Minor)) 33 | if err != nil { 34 | return nil, fmt.Errorf("parsing minor version: %w", err) 35 | } 36 | 37 | return &Version{v: sv, m: m}, nil 38 | } 39 | 40 | type Version struct { 41 | v *version.Info 42 | m int 43 | } 44 | 45 | func (v *Version) Full() string { 46 | return v.v.Major + "." + v.v.Minor 47 | } 48 | 49 | func (v *Version) MinorInt() int { 50 | return v.m 51 | } 52 | -------------------------------------------------------------------------------- /collie/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/values-custom.yaml: -------------------------------------------------------------------------------- 1 | # values.yaml 2 | 3 | ## Grafana configuration 4 | service: 5 | type: ClusterIP 6 | port: 3000 7 | ingress: 8 | enabled: false 9 | annotations: 10 | kubernetes.io/ingress.class: nginx 11 | hosts: 12 | - collie-dev.org 13 | paths: 14 | - / 15 | adminUser: admin 16 | adminPassword: admin 17 | 18 | 19 | ## Elasticsearch datasource configuration 20 | datasources: 21 | datasources.yaml: 22 | apiVersion: 1 23 | datasources: 24 | - name: es-collie-k8s-elastic 25 | type: elasticsearch 26 | access: proxy 27 | url: https://elasticsearch-master:9200 28 | database: collie-k8s-elastic 29 | basicAuth: true 30 | basicAuthUser: elastic 31 | isDefault: true 32 | readOnly: false 33 | editable: true 34 | jsonData: 35 | esVersion: 8 36 | tlsSkipVerify: true 37 | secureJsonData: 38 | basicAuthPassword: ${ES_PASSWORD} 39 | 40 | grafana.ini: 41 | auth.anonymous: 42 | enabled: true 43 | org_name: "Main Org." # Change it to the name of your default organization 44 | org_role: "Viewer" # The role assigned to anonymous users in the organization 45 | 46 | dashboards: 47 | default: 48 | # some-dashboard: 49 | # json: | 50 | # $RAW_JSON 51 | custom-dashboard: 52 | file: dashboards/custom-dashboard.json 53 | -------------------------------------------------------------------------------- /api-server/model/bottle.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023-2024 Omnissa, LLC. 3 | SPDX-License-Identifier: Apache-2.0 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package model 17 | 18 | // Bottle example 19 | type Bottle struct { 20 | ID int `json:"id" example:"1"` 21 | Name string `json:"name" example:"bottle_name"` 22 | Account Account `json:"account"` 23 | } 24 | 25 | // BottlesAll example 26 | func BottlesAll() ([]Bottle, error) { 27 | return bottles, nil 28 | } 29 | 30 | // BottleOne example 31 | func BottleOne(id int) (*Bottle, error) { 32 | for _, v := range bottles { 33 | if id == v.ID { 34 | return &v, nil 35 | } 36 | } 37 | return nil, ErrNoRow 38 | } 39 | 40 | var bottles = []Bottle{ 41 | {ID: 1, Name: "bottle_1", Account: Account{ID: 1, Name: "accout_1"}}, 42 | {ID: 2, Name: "bottle_2", Account: Account{ID: 2, Name: "accout_2"}}, 43 | {ID: 3, Name: "bottle_3", Account: Account{ID: 3, Name: "accout_3"}}, 44 | } 45 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/role.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.rbac.create (not .Values.rbac.useExistingRole) -}} 2 | apiVersion: {{ include "grafana.rbac.apiVersion" . }} 3 | kind: Role 4 | metadata: 5 | name: {{ include "grafana.fullname" . }} 6 | namespace: {{ include "grafana.namespace" . }} 7 | labels: 8 | {{- include "grafana.labels" . | nindent 4 }} 9 | {{- with .Values.annotations }} 10 | annotations: 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | {{- if or .Values.rbac.pspEnabled (and .Values.rbac.namespaced (or .Values.sidecar.dashboards.enabled .Values.sidecar.datasources.enabled .Values.sidecar.plugins.enabled .Values.rbac.extraRoleRules)) }} 14 | rules: 15 | {{- if and .Values.rbac.pspEnabled (.Capabilities.APIVersions.Has "policy/v1beta1/PodSecurityPolicy") }} 16 | - apiGroups: ['extensions'] 17 | resources: ['podsecuritypolicies'] 18 | verbs: ['use'] 19 | resourceNames: [{{ include "grafana.fullname" . }}] 20 | {{- end }} 21 | {{- if and .Values.rbac.namespaced (or .Values.sidecar.dashboards.enabled .Values.sidecar.datasources.enabled .Values.sidecar.plugins.enabled) }} 22 | - apiGroups: [""] # "" indicates the core API group 23 | resources: ["configmaps", "secrets"] 24 | verbs: ["get", "watch", "list"] 25 | {{- end }} 26 | {{- with .Values.rbac.extraRoleRules }} 27 | {{- toYaml . | nindent 2 }} 28 | {{- end}} 29 | {{- else }} 30 | rules: [] 31 | {{- end }} 32 | {{- end }} 33 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/configmap-dashboard-provider.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.sidecar.dashboards.enabled .Values.sidecar.dashboards.SCProvider }} 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | labels: 6 | {{- include "grafana.labels" . | nindent 4 }} 7 | {{- with .Values.annotations }} 8 | annotations: 9 | {{- toYaml . | nindent 4 }} 10 | {{- end }} 11 | name: {{ include "grafana.fullname" . }}-config-dashboards 12 | namespace: {{ include "grafana.namespace" . }} 13 | data: 14 | provider.yaml: |- 15 | apiVersion: 1 16 | providers: 17 | - name: '{{ .Values.sidecar.dashboards.provider.name }}' 18 | orgId: {{ .Values.sidecar.dashboards.provider.orgid }} 19 | {{- if not .Values.sidecar.dashboards.provider.foldersFromFilesStructure }} 20 | folder: '{{ .Values.sidecar.dashboards.provider.folder }}' 21 | {{- end }} 22 | type: {{ .Values.sidecar.dashboards.provider.type }} 23 | disableDeletion: {{ .Values.sidecar.dashboards.provider.disableDelete }} 24 | allowUiUpdates: {{ .Values.sidecar.dashboards.provider.allowUiUpdates }} 25 | updateIntervalSeconds: {{ .Values.sidecar.dashboards.provider.updateIntervalSeconds | default 30 }} 26 | options: 27 | foldersFromFilesStructure: {{ .Values.sidecar.dashboards.provider.foldersFromFilesStructure }} 28 | path: {{ .Values.sidecar.dashboards.folder }}{{- with .Values.sidecar.dashboards.defaultFolderName }}/{{ . }}{{- end }} 29 | {{- end }} 30 | -------------------------------------------------------------------------------- /collie/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 37 | -------------------------------------------------------------------------------- /k8s-agent/internal/model/model.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023-2024 Omnissa, LLC. 3 | SPDX-License-Identifier: Apache-2.0 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package model 17 | 18 | type ComplianceRecord struct { 19 | Timestamp string `json:"@timestamp"` 20 | OrgId string `json:"orgId"` 21 | ClusterId string `json:"clusterId"` 22 | RuleId string `json:"ruleId"` 23 | Severity string `json:"severity"` 24 | Url string `json:"url"` 25 | Data map[string]string `json:"data"` 26 | } 27 | 28 | type ClusterInfo struct { 29 | Provider string `json:"provider"` 30 | 31 | Data interface{} `json:"data"` 32 | } 33 | 34 | type Compliance struct { 35 | Plugin string `json:"plugin"` 36 | RuleId string `json:"ruleId"` 37 | Category string `json:"category"` 38 | Subcategory string `json:"subcategory"` 39 | Description string `json:"description"` 40 | Status string `json:"status"` //FAIL, PASS, WARN 41 | Remediation string `json:"remediation"` 42 | } 43 | -------------------------------------------------------------------------------- /api-server/util/util.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023-2024 Omnissa, LLC. 3 | SPDX-License-Identifier: Apache-2.0 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package util 17 | 18 | import ( 19 | "crypto/rand" 20 | "encoding/hex" 21 | "encoding/json" 22 | "fmt" 23 | "reflect" 24 | ) 25 | 26 | func RandomString(n int) string { 27 | bytes := make([]byte, n) 28 | _, err := rand.Read(bytes) 29 | if err != nil { 30 | panic(fmt.Errorf("Error generating random: %s", err)) 31 | } 32 | return hex.EncodeToString(bytes) 33 | } 34 | 35 | func ToJson(data interface{}) string { 36 | b, err := json.MarshalIndent(data, "", " ") 37 | if err != nil { 38 | return "Error: " + err.Error() 39 | } 40 | return string(b) 41 | } 42 | 43 | func DeepCopyMap(from map[string]interface{}) map[string]interface{} { 44 | to := make(map[string]interface{}) 45 | 46 | for k, v := range from { 47 | if v != nil && reflect.TypeOf(v).Kind() == reflect.Map { 48 | valueMap := v.(map[string]interface{}) 49 | to[k] = DeepCopyMap(valueMap) 50 | } else { 51 | to[k] = v 52 | } 53 | } 54 | return to 55 | } 56 | -------------------------------------------------------------------------------- /api-server/service/persist/persist.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023-2024 Omnissa, LLC. 3 | SPDX-License-Identifier: Apache-2.0 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package persist 17 | 18 | import ( 19 | "errors" 20 | 21 | _ "k8s.io/utils/lru" 22 | ) 23 | 24 | var ( 25 | // TODO: change to persist store 26 | collections map[string]Store 27 | ) 28 | 29 | func init() { 30 | collections = map[string]Store{} 31 | } 32 | 33 | type Store interface { 34 | Put(id string, data interface{}) 35 | Get(id string) (interface{}, error) 36 | } 37 | 38 | type storeImpl struct { 39 | data map[string]interface{} 40 | } 41 | 42 | func Collection(name string) Store { 43 | s, ok := collections[name] 44 | if !ok { 45 | s = &storeImpl{data: map[string]interface{}{}} 46 | collections[name] = s 47 | } 48 | return s 49 | } 50 | 51 | func (s *storeImpl) Put(id string, data interface{}) { 52 | s.data[id] = data 53 | } 54 | 55 | func (s *storeImpl) Get(id string) (interface{}, error) { 56 | v, exist := s.data[id] 57 | if exist { 58 | return v, nil 59 | } 60 | return nil, errors.New("Item not found: " + id) 61 | } 62 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/podsecuritypolicy.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.rbac.pspEnabled (.Capabilities.APIVersions.Has "policy/v1beta1/PodSecurityPolicy") }} 2 | apiVersion: policy/v1beta1 3 | kind: PodSecurityPolicy 4 | metadata: 5 | name: {{ include "grafana.fullname" . }} 6 | labels: 7 | {{- include "grafana.labels" . | nindent 4 }} 8 | annotations: 9 | seccomp.security.alpha.kubernetes.io/allowedProfileNames: 'docker/default,runtime/default' 10 | seccomp.security.alpha.kubernetes.io/defaultProfileName: 'docker/default' 11 | {{- if .Values.rbac.pspUseAppArmor }} 12 | apparmor.security.beta.kubernetes.io/allowedProfileNames: 'runtime/default' 13 | apparmor.security.beta.kubernetes.io/defaultProfileName: 'runtime/default' 14 | {{- end }} 15 | spec: 16 | privileged: false 17 | allowPrivilegeEscalation: false 18 | requiredDropCapabilities: 19 | # Default set from Docker, with DAC_OVERRIDE and CHOWN 20 | - ALL 21 | volumes: 22 | - 'configMap' 23 | - 'emptyDir' 24 | - 'projected' 25 | - 'csi' 26 | - 'secret' 27 | - 'downwardAPI' 28 | - 'persistentVolumeClaim' 29 | hostNetwork: false 30 | hostIPC: false 31 | hostPID: false 32 | runAsUser: 33 | rule: 'RunAsAny' 34 | seLinux: 35 | rule: 'RunAsAny' 36 | supplementalGroups: 37 | rule: 'MustRunAs' 38 | ranges: 39 | # Forbid adding the root group. 40 | - min: 1 41 | max: 65535 42 | fsGroup: 43 | rule: 'MustRunAs' 44 | ranges: 45 | # Forbid adding the root group. 46 | - min: 1 47 | max: 65535 48 | readOnlyRootFilesystem: false 49 | {{- end }} 50 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/servicemonitor.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceMonitor.enabled }} 2 | --- 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | name: {{ include "grafana.fullname" . }} 7 | {{- if .Values.serviceMonitor.namespace }} 8 | namespace: {{ tpl .Values.serviceMonitor.namespace . }} 9 | {{- else }} 10 | namespace: {{ include "grafana.namespace" . }} 11 | {{- end }} 12 | labels: 13 | {{- include "grafana.labels" . | nindent 4 }} 14 | {{- with .Values.serviceMonitor.labels }} 15 | {{- toYaml . | nindent 4 }} 16 | {{- end }} 17 | spec: 18 | endpoints: 19 | - port: {{ .Values.service.portName }} 20 | {{- with .Values.serviceMonitor.interval }} 21 | interval: {{ . }} 22 | {{- end }} 23 | {{- with .Values.serviceMonitor.scrapeTimeout }} 24 | scrapeTimeout: {{ . }} 25 | {{- end }} 26 | honorLabels: true 27 | path: {{ .Values.serviceMonitor.path }} 28 | scheme: {{ .Values.serviceMonitor.scheme }} 29 | {{- with .Values.serviceMonitor.tlsConfig }} 30 | tlsConfig: 31 | {{- toYaml . | nindent 6 }} 32 | {{- end }} 33 | {{- with .Values.serviceMonitor.relabelings }} 34 | relabelings: 35 | {{- toYaml . | nindent 6 }} 36 | {{- end }} 37 | jobLabel: "{{ .Release.Name }}" 38 | selector: 39 | matchLabels: 40 | {{- include "grafana.selectorLabels" . | nindent 6 }} 41 | namespaceSelector: 42 | matchNames: 43 | - {{ include "grafana.namespace" . }} 44 | {{- with .Values.serviceMonitor.targetLabels }} 45 | targetLabels: 46 | {{- toYaml . | nindent 4 }} 47 | {{- end }} 48 | {{- end }} 49 | -------------------------------------------------------------------------------- /api-server/service/org/org.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023-2024 Omnissa, LLC. 3 | SPDX-License-Identifier: Apache-2.0 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package org 17 | 18 | import ( 19 | "collie-api-server/config" 20 | "collie-api-server/service/persist" 21 | ) 22 | 23 | type OrgInfo struct { 24 | OrgId string 25 | EsKey string 26 | GrafanaOrgId string 27 | } 28 | 29 | var ( 30 | orgColl persist.Store 31 | ) 32 | 33 | func init() { 34 | orgColl = persist.Collection("org") 35 | } 36 | 37 | func Get(orgId string) (*OrgInfo, error) { 38 | ret, err := orgColl.Get(orgId) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return ret.(*OrgInfo), nil 43 | } 44 | 45 | func EnsureOnboard(orgId string) (*OrgInfo, error) { 46 | orgInfo, err := Get(orgId) 47 | if err != nil { 48 | orgInfo = &OrgInfo{} 49 | orgInfo.EsKey = createEsTenant(orgId) 50 | orgInfo.GrafanaOrgId = createGrafanaTenant(orgId) 51 | orgColl.Put(orgId, orgInfo) 52 | } 53 | 54 | return orgInfo, nil 55 | } 56 | 57 | func createEsTenant(orgId string) string { 58 | return config.Get().EsKey 59 | } 60 | 61 | func createGrafanaTenant(orgId string) string { 62 | return "1" 63 | } 64 | -------------------------------------------------------------------------------- /deployment/scripts/minikube/deploy-es.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | helm list -n collie-server -a | grep es > /dev/null 2>&1 4 | if [ $? -eq 0 ]; then 5 | helm uninstall -n collie-server es --wait 6 | fi 7 | 8 | echo Deploy ES... 9 | helm install -n collie-server es elastic/elasticsearch -f ./es/values.yaml -f ./es/values-custom.yaml --wait 10 | 11 | sleep 10 12 | 13 | export ES_PASSWORD=$(kubectl get -n collie-server secret/elasticsearch-master-credentials -o jsonpath="{.data.password}" | base64 -d) 14 | 15 | ES_USER=$(kubectl get -n collie-server secret/elasticsearch-master-credentials -o jsonpath="{.data.username}" | base64 -d) 16 | 17 | ES_AUTH=$ES_USER:$ES_PASSWORD 18 | ES_URL=https://collie-dev.org:9200 19 | 20 | echo ES_AUTH: $ES_AUTH 21 | Echo Forwarding ES in background... 22 | 23 | source ./kill-processes-by-keyword.sh "kubectl port-forward -n collie-server --address 0.0.0.0 services/elasticsearch-master" 24 | kubectl port-forward -n collie-server --address 0.0.0.0 services/elasticsearch-master 9200:9200 & 25 | 26 | sleep 5 27 | 28 | Echo Clean up ES data... 29 | curl -k -u $ES_AUTH -X DELETE $ES_URL/collie-k8s-elastic 30 | Echo Recreate ES index... 31 | curl -k -u $ES_AUTH -X PUT -H "Content-Type: application/json" -d ' 32 | { 33 | "mappings": { 34 | "properties": { 35 | "resource": { 36 | "dynamic": false, 37 | "properties": { 38 | "metadata": { 39 | "properties": { 40 | "name": { 41 | "type": "text" 42 | }, 43 | "namespace": { 44 | "type": "text" 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | }' $ES_URL/collie-k8s-elastic 53 | 54 | echo 55 | 56 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/networkpolicy.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.networkPolicy.enabled }} 2 | apiVersion: networking.k8s.io/v1 3 | kind: NetworkPolicy 4 | metadata: 5 | name: {{ include "grafana.fullname" . }} 6 | namespace: {{ include "grafana.namespace" . }} 7 | labels: 8 | {{- include "grafana.labels" . | nindent 4 }} 9 | {{- with .Values.labels }} 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- with .Values.annotations }} 13 | annotations: 14 | {{- toYaml . | nindent 4 }} 15 | {{- end }} 16 | spec: 17 | policyTypes: 18 | {{- if .Values.networkPolicy.ingress }} 19 | - Ingress 20 | {{- end }} 21 | {{- if .Values.networkPolicy.egress.enabled }} 22 | - Egress 23 | {{- end }} 24 | podSelector: 25 | matchLabels: 26 | {{- include "grafana.selectorLabels" . | nindent 6 }} 27 | 28 | {{- if .Values.networkPolicy.egress.enabled }} 29 | egress: 30 | - ports: 31 | {{ .Values.networkPolicy.egress.ports | toJson }} 32 | {{- end }} 33 | {{- if .Values.networkPolicy.ingress }} 34 | ingress: 35 | - ports: 36 | - port: {{ .Values.service.targetPort }} 37 | {{- if not .Values.networkPolicy.allowExternal }} 38 | from: 39 | - podSelector: 40 | matchLabels: 41 | {{ include "grafana.fullname" . }}-client: "true" 42 | {{- with .Values.networkPolicy.explicitNamespacesSelector }} 43 | - namespaceSelector: 44 | {{- toYaml . | nindent 12 }} 45 | {{- end }} 46 | - podSelector: 47 | matchLabels: 48 | {{- include "grafana.labels" . | nindent 14 }} 49 | role: read 50 | {{- end }} 51 | {{- end }} 52 | {{- end }} 53 | -------------------------------------------------------------------------------- /deployment/scripts/azure/deploy-es.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | helm list -n collie-server -a | grep es > /dev/null 2>&1 4 | if [ $? -eq 0 ]; then 5 | helm uninstall -n collie-server es --wait 6 | fi 7 | 8 | echo Deploy ES... 9 | cd ../../helm-charts 10 | helm install -n collie-server es elastic/elasticsearch -f ./es/values.yaml -f ./es/values-custom.yaml --wait 11 | 12 | sleep 10 13 | 14 | export ES_PASSWORD=$(kubectl get -n collie-server secret/elasticsearch-master-credentials -o jsonpath="{.data.password}" | base64 -d) 15 | 16 | ES_USER=$(kubectl get -n collie-server secret/elasticsearch-master-credentials -o jsonpath="{.data.username}" | base64 -d) 17 | 18 | ES_AUTH=$ES_USER:$ES_PASSWORD 19 | ES_URL=https://collie-dev.org:9200 20 | 21 | echo ES_AUTH: $ES_AUTH 22 | Echo Forwarding ES in background... 23 | 24 | source ./kill-processes-by-keyword.sh "kubectl port-forward -n collie-server --address 0.0.0.0 services/elasticsearch-master" 25 | kubectl port-forward -n collie-server --address 0.0.0.0 services/elasticsearch-master 9200:9200 & 26 | 27 | sleep 5 28 | 29 | Echo Clean up ES data... 30 | curl -k -u $ES_AUTH -X DELETE $ES_URL/collie-k8s-elastic 31 | Echo Recreate ES index... 32 | curl -k -u $ES_AUTH -X PUT -H "Content-Type: application/json" -d ' 33 | { 34 | "mappings": { 35 | "properties": { 36 | "resource": { 37 | "dynamic": false, 38 | "properties": { 39 | "metadata": { 40 | "properties": { 41 | "name": { 42 | "type": "text" 43 | }, 44 | "namespace": { 45 | "type": "text" 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | } 53 | }' $ES_URL/collie-k8s-elastic 54 | 55 | echo 56 | 57 | -------------------------------------------------------------------------------- /deployment/scripts/portal.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: portal 5 | namespace: collie-server 6 | labels: 7 | app.kubernetes.io/name: portal 8 | app.kubernetes.io/instance: portal 9 | app.kubernetes.io/version: "v1" 10 | app.kubernetes.io/managed-by: collie 11 | 12 | spec: 13 | replicas: 1 14 | selector: 15 | matchLabels: 16 | app.kubernetes.io/name: portal 17 | app.kubernetes.io/instance: portal 18 | template: 19 | metadata: 20 | labels: 21 | app.kubernetes.io/name: portal 22 | app.kubernetes.io/instance: portal 23 | spec: 24 | containers: 25 | - name: portal 26 | image: collie.azurecr.io/collie-portal:1 27 | imagePullPolicy: IfNotPresent 28 | ports: 29 | - containerPort: 8080 30 | name: http 31 | - containerPort: 9876 32 | name: healthz 33 | readinessProbe: 34 | httpGet: 35 | port: healthz 36 | livenessProbe: 37 | httpGet: 38 | port: healthz 39 | --- 40 | apiVersion: v1 41 | kind: Service 42 | metadata: 43 | labels: 44 | app.kubernetes.io/instance: portal 45 | app.kubernetes.io/managed-by: collie 46 | app.kubernetes.io/name: portal 47 | app.kubernetes.io/version: v1 48 | name: portal 49 | namespace: collie-server 50 | spec: 51 | internalTrafficPolicy: Cluster 52 | ipFamilies: 53 | - IPv4 54 | ipFamilyPolicy: SingleStack 55 | ports: 56 | - name: http 57 | port: 8080 58 | protocol: TCP 59 | targetPort: 8080 60 | selector: 61 | app.kubernetes.io/instance: portal 62 | app.kubernetes.io/name: portal 63 | sessionAffinity: None 64 | type: ClusterIP 65 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/tests/test.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.testFramework.enabled }} 2 | {{- $root := . }} 3 | apiVersion: v1 4 | kind: Pod 5 | metadata: 6 | name: {{ include "grafana.fullname" . }}-test 7 | labels: 8 | {{- include "grafana.labels" . | nindent 4 }} 9 | annotations: 10 | "helm.sh/hook": test-success 11 | "helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded" 12 | namespace: {{ include "grafana.namespace" . }} 13 | spec: 14 | serviceAccountName: {{ include "grafana.serviceAccountNameTest" . }} 15 | {{- with .Values.testFramework.securityContext }} 16 | securityContext: 17 | {{- toYaml . | nindent 4 }} 18 | {{- end }} 19 | {{- if or .Values.image.pullSecrets .Values.global.imagePullSecrets }} 20 | imagePullSecrets: 21 | {{- include "grafana.imagePullSecrets" (dict "root" $root "imagePullSecrets" .Values.image.pullSecrets) | nindent 4 }} 22 | {{- end }} 23 | {{- with .Values.nodeSelector }} 24 | nodeSelector: 25 | {{- toYaml . | nindent 4 }} 26 | {{- end }} 27 | {{- with .Values.affinity }} 28 | affinity: 29 | {{- tpl (toYaml .) $root | nindent 4 }} 30 | {{- end }} 31 | {{- with .Values.tolerations }} 32 | tolerations: 33 | {{- toYaml . | nindent 4 }} 34 | {{- end }} 35 | containers: 36 | - name: {{ .Release.Name }}-test 37 | image: "{{ .Values.testFramework.image}}:{{ .Values.testFramework.tag }}" 38 | imagePullPolicy: "{{ .Values.testFramework.imagePullPolicy}}" 39 | command: ["/opt/bats/bin/bats", "-t", "/tests/run.sh"] 40 | volumeMounts: 41 | - mountPath: /tests 42 | name: tests 43 | readOnly: true 44 | volumes: 45 | - name: tests 46 | configMap: 47 | name: {{ include "grafana.fullname" . }}-test 48 | restartPolicy: Never 49 | {{- end }} 50 | -------------------------------------------------------------------------------- /deployment/helm-charts/api-server/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | {{- range .paths }} 5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "..fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "..fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "..fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "..name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") 20 | echo "Visit http://127.0.0.1:8080 to use your application" 21 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT 22 | {{- end }} 23 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/image-renderer-servicemonitor.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.imageRenderer.serviceMonitor.enabled }} 2 | --- 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | name: {{ include "grafana.fullname" . }}-image-renderer 7 | {{- if .Values.imageRenderer.serviceMonitor.namespace }} 8 | namespace: {{ tpl .Values.imageRenderer.serviceMonitor.namespace . }} 9 | {{- else }} 10 | namespace: {{ include "grafana.namespace" . }} 11 | {{- end }} 12 | labels: 13 | {{- include "grafana.imageRenderer.labels" . | nindent 4 }} 14 | {{- with .Values.imageRenderer.serviceMonitor.labels }} 15 | {{- toYaml . | nindent 4 }} 16 | {{- end }} 17 | spec: 18 | endpoints: 19 | - port: {{ .Values.imageRenderer.service.portName }} 20 | {{- with .Values.imageRenderer.serviceMonitor.interval }} 21 | interval: {{ . }} 22 | {{- end }} 23 | {{- with .Values.imageRenderer.serviceMonitor.scrapeTimeout }} 24 | scrapeTimeout: {{ . }} 25 | {{- end }} 26 | honorLabels: true 27 | path: {{ .Values.imageRenderer.serviceMonitor.path }} 28 | scheme: {{ .Values.imageRenderer.serviceMonitor.scheme }} 29 | {{- with .Values.imageRenderer.serviceMonitor.tlsConfig }} 30 | tlsConfig: 31 | {{- toYaml . | nindent 6 }} 32 | {{- end }} 33 | {{- with .Values.imageRenderer.serviceMonitor.relabelings }} 34 | relabelings: 35 | {{- toYaml . | nindent 6 }} 36 | {{- end }} 37 | jobLabel: "{{ .Release.Name }}-image-renderer" 38 | selector: 39 | matchLabels: 40 | {{- include "grafana.imageRenderer.selectorLabels" . | nindent 6 }} 41 | namespaceSelector: 42 | matchNames: 43 | - {{ include "grafana.namespace" . }} 44 | {{- with .Values.imageRenderer.serviceMonitor.targetLabels }} 45 | targetLabels: 46 | {{- toYaml . | nindent 4 }} 47 | {{- end }} 48 | {{- end }} 49 | -------------------------------------------------------------------------------- /api-server/controller/report.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023-2024 Omnissa, LLC. 3 | SPDX-License-Identifier: Apache-2.0 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package controller 17 | 18 | import ( 19 | "github.com/gin-gonic/gin" 20 | "log" 21 | "net/http" 22 | ) 23 | 24 | // SyncStart godoc 25 | // 26 | // @Summary Indicating a sync has been started 27 | // @Description Indicating a sync has been started 28 | // @Tags agent 29 | // @Accept json 30 | // @Produce json 31 | // @Success 200 {object} string 32 | // @Failure 400 {object} httputil.HTTPError 33 | // @Failure 404 {object} httputil.HTTPError 34 | // @Failure 500 {object} httputil.HTTPError 35 | // @Router /agent/sync-start [post] 36 | func (c *Controller) SyncStart(ctx *gin.Context) { 37 | log.Printf("SyncStart") 38 | ctx.String(http.StatusOK, "") 39 | } 40 | 41 | // PostDiscoveryComplete godoc 42 | // 43 | // @Summary Indicating a sync has complete 44 | // @Description Indicating a sync has complete 45 | // @Tags agent 46 | // @Accept json 47 | // @Produce json 48 | // @Success 200 {object} string 49 | // @Failure 400 {object} httputil.HTTPError 50 | // @Failure 404 {object} httputil.HTTPError 51 | // @Failure 500 {object} httputil.HTTPError 52 | // @Router /agent/sync-complete [post] 53 | func (c *Controller) SyncComplete(ctx *gin.Context) { 54 | log.Printf("SyncComplete") 55 | ctx.String(http.StatusOK, "") 56 | } 57 | -------------------------------------------------------------------------------- /api-server/service/auth/authInfo.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023-2024 Omnissa, LLC. 3 | SPDX-License-Identifier: Apache-2.0 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package auth 17 | 18 | import ( 19 | "collie-api-server/util" 20 | "reflect" 21 | ) 22 | 23 | type AuthInfo interface { 24 | OrgId() string 25 | Username() string 26 | Get(name string) string 27 | GetAny(name string) interface{} 28 | Set(name string, value interface{}) 29 | AsMap() map[string]interface{} 30 | } 31 | 32 | type defaultAuthInfo struct { 33 | data map[string]interface{} 34 | } 35 | 36 | func FromMap(data map[string]interface{}) AuthInfo { 37 | return defaultAuthInfo{data: util.DeepCopyMap(data)} 38 | } 39 | 40 | func (t defaultAuthInfo) OrgId() string { 41 | return "elastic" 42 | //return t.Get("orgId") 43 | } 44 | 45 | func (t defaultAuthInfo) Username() string { 46 | return t.Get("username") 47 | } 48 | 49 | func (t defaultAuthInfo) Get(name string) string { 50 | v, ok := t.data[name] 51 | if !ok { 52 | return "" 53 | } 54 | if reflect.TypeOf(v) == reflect.TypeOf("") { 55 | return v.(string) 56 | } 57 | return "" 58 | } 59 | 60 | func (t defaultAuthInfo) GetAny(name string) interface{} { 61 | return t.data[name] 62 | } 63 | 64 | func (t defaultAuthInfo) Set(name string, value interface{}) { 65 | t.data[name] = value 66 | } 67 | 68 | func (t defaultAuthInfo) AsMap() map[string]interface{} { 69 | return util.DeepCopyMap(t.data) 70 | } 71 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/service.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.service.enabled }} 2 | {{- $root := . }} 3 | apiVersion: v1 4 | kind: Service 5 | metadata: 6 | name: {{ include "grafana.fullname" . }} 7 | namespace: {{ include "grafana.namespace" . }} 8 | labels: 9 | {{- include "grafana.labels" . | nindent 4 }} 10 | {{- with .Values.service.labels }} 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | {{- with .Values.service.annotations }} 14 | annotations: 15 | {{- tpl (toYaml . | nindent 4) $root }} 16 | {{- end }} 17 | spec: 18 | {{- if (or (eq .Values.service.type "ClusterIP") (empty .Values.service.type)) }} 19 | type: ClusterIP 20 | {{- with .Values.service.clusterIP }} 21 | clusterIP: {{ . }} 22 | {{- end }} 23 | {{- else if eq .Values.service.type "LoadBalancer" }} 24 | type: {{ .Values.service.type }} 25 | {{- with .Values.service.loadBalancerIP }} 26 | loadBalancerIP: {{ . }} 27 | {{- end }} 28 | {{- with .Values.service.loadBalancerSourceRanges }} 29 | loadBalancerSourceRanges: 30 | {{- toYaml . | nindent 4 }} 31 | {{- end }} 32 | {{- else }} 33 | type: {{ .Values.service.type }} 34 | {{- end }} 35 | {{- with .Values.service.externalIPs }} 36 | externalIPs: 37 | {{- toYaml . | nindent 4 }} 38 | {{- end }} 39 | ports: 40 | - name: {{ .Values.service.portName }} 41 | port: {{ .Values.service.port }} 42 | protocol: TCP 43 | targetPort: {{ .Values.service.targetPort }} 44 | {{- with .Values.service.appProtocol }} 45 | appProtocol: {{ . }} 46 | {{- end }} 47 | {{- if (and (eq .Values.service.type "NodePort") (not (empty .Values.service.nodePort))) }} 48 | nodePort: {{ .Values.service.nodePort }} 49 | {{- end }} 50 | {{- with .Values.extraExposePorts }} 51 | {{- tpl (toYaml . | nindent 4) $root }} 52 | {{- end }} 53 | selector: 54 | {{- include "grafana.selectorLabels" . | nindent 4 }} 55 | {{- end }} 56 | -------------------------------------------------------------------------------- /api-server/service/template/cluster-role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: agent 5 | labels: 6 | app.kubernetes.io/name: agent 7 | app.kubernetes.io/instance: agent 8 | app.kubernetes.io/version: "v1" 9 | app.kubernetes.io/managed-by: collie 10 | 11 | rules: 12 | # --- 13 | # Required for cost savings estimation features. 14 | # --- 15 | - apiGroups: 16 | - "" 17 | resources: 18 | - pods 19 | - nodes 20 | - replicationcontrollers 21 | - persistentvolumeclaims 22 | - persistentvolumes 23 | - services 24 | - namespaces 25 | - events 26 | verbs: 27 | - get 28 | - list 29 | - watch 30 | - apiGroups: 31 | - "" 32 | resources: 33 | - namespaces 34 | verbs: 35 | - get 36 | - apiGroups: 37 | - "apps" 38 | resources: 39 | - deployments 40 | - replicasets 41 | - daemonsets 42 | - statefulsets 43 | verbs: 44 | - get 45 | - list 46 | - watch 47 | - apiGroups: 48 | - "storage.k8s.io" 49 | resources: 50 | - storageclasses 51 | - csinodes 52 | verbs: 53 | - get 54 | - list 55 | - watch 56 | - apiGroups: 57 | - "batch" 58 | resources: 59 | - jobs 60 | verbs: 61 | - get 62 | - list 63 | - watch 64 | - apiGroups: 65 | - "autoscaling" 66 | resources: 67 | - horizontalpodautoscalers 68 | verbs: 69 | - get 70 | - list 71 | - watch 72 | - apiGroups: 73 | - "coordination.k8s.io" 74 | resources: 75 | - leases 76 | verbs: 77 | - create 78 | - get 79 | - list 80 | - watch 81 | - update 82 | - apiGroups: 83 | - "metrics.k8s.io" 84 | resources: 85 | - pods 86 | verbs: 87 | - get 88 | - list 89 | - nonResourceURLs: 90 | - "/version" 91 | verbs: 92 | - "get" -------------------------------------------------------------------------------- /deployment/k8s/azure/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "resourceName": { 6 | "value": "collie" 7 | }, 8 | "location": { 9 | "value": "westus2" 10 | }, 11 | "dnsPrefix": { 12 | "value": "collie-dns" 13 | }, 14 | "kubernetesVersion": { 15 | "value": "1.25.6" 16 | }, 17 | "networkPlugin": { 18 | "value": "azure" 19 | }, 20 | "enableRBAC": { 21 | "value": true 22 | }, 23 | "nodeResourceGroup": { 24 | "value": "MC_collie_collie_westus2" 25 | }, 26 | "upgradeChannel": { 27 | "value": "patch" 28 | }, 29 | "adminGroupObjectIDs": { 30 | "value": [] 31 | }, 32 | "disableLocalAccounts": { 33 | "value": false 34 | }, 35 | "azureRbac": { 36 | "value": false 37 | }, 38 | "enablePrivateCluster": { 39 | "value": false 40 | }, 41 | "enableAzurePolicy": { 42 | "value": false 43 | }, 44 | "enableSecretStoreCSIDriver": { 45 | "value": false 46 | }, 47 | "vmssNodePool": { 48 | "value": true 49 | }, 50 | "vnetSubnetID": { 51 | "value": "/subscriptions/a201beee-0dd8-4336-b889-89b223a1a393/resourceGroups/collie/providers/Microsoft.Network/virtualNetworks/collie-vnet/subnets/default" 52 | }, 53 | "serviceCidr": { 54 | "value": "10.0.0.0/16" 55 | }, 56 | "dnsServiceIP": { 57 | "value": "10.0.0.10" 58 | }, 59 | "acrName": { 60 | "value": "collie" 61 | }, 62 | "acrResourceGroup": { 63 | "value": "nanw" 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /deployment/helm-charts/api-server/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "..name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "..fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "..chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "..labels" -}} 37 | helm.sh/chart: {{ include "..chart" . }} 38 | {{ include "..selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "..selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "..name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "..serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "..fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- $sts := list "sts" "StatefulSet" "statefulset" -}} 2 | {{- if .Values.autoscaling.enabled }} 3 | apiVersion: {{ include "grafana.hpa.apiVersion" . }} 4 | kind: HorizontalPodAutoscaler 5 | metadata: 6 | name: {{ include "grafana.fullname" . }} 7 | namespace: {{ include "grafana.namespace" . }} 8 | labels: 9 | app.kubernetes.io/name: {{ include "grafana.name" . }} 10 | helm.sh/chart: {{ include "grafana.chart" . }} 11 | app.kubernetes.io/managed-by: {{ .Release.Service }} 12 | app.kubernetes.io/instance: {{ .Release.Name }} 13 | spec: 14 | scaleTargetRef: 15 | apiVersion: apps/v1 16 | {{- if has .Values.persistence.type $sts }} 17 | kind: StatefulSet 18 | {{- else }} 19 | kind: Deployment 20 | {{- end }} 21 | name: {{ include "grafana.fullname" . }} 22 | minReplicas: {{ .Values.autoscaling.minReplicas }} 23 | maxReplicas: {{ .Values.autoscaling.maxReplicas }} 24 | metrics: 25 | {{- if .Values.autoscaling.targetMemory }} 26 | - type: Resource 27 | resource: 28 | name: memory 29 | {{- if eq (include "grafana.hpa.apiVersion" .) "autoscaling/v2beta1" }} 30 | targetAverageUtilization: {{ .Values.autoscaling.targetMemory }} 31 | {{- else }} 32 | target: 33 | type: Utilization 34 | averageUtilization: {{ .Values.autoscaling.targetMemory }} 35 | {{- end }} 36 | {{- end }} 37 | {{- if .Values.autoscaling.targetCPU }} 38 | - type: Resource 39 | resource: 40 | name: cpu 41 | {{- if eq (include "grafana.hpa.apiVersion" .) "autoscaling/v2beta1" }} 42 | targetAverageUtilization: {{ .Values.autoscaling.targetCPU }} 43 | {{- else }} 44 | target: 45 | type: Utilization 46 | averageUtilization: {{ .Values.autoscaling.targetCPU }} 47 | {{- end }} 48 | {{- end }} 49 | {{- if .Values.autoscaling.behavior }} 50 | behavior: {{ toYaml .Values.autoscaling.behavior | nindent 4 }} 51 | {{- end }} 52 | {{- end }} 53 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/image-renderer-hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.imageRenderer.enabled .Values.imageRenderer.autoscaling.enabled }} 2 | apiVersion: {{ include "grafana.hpa.apiVersion" . }} 3 | kind: HorizontalPodAutoscaler 4 | metadata: 5 | name: {{ include "grafana.fullname" . }}-image-renderer 6 | namespace: {{ include "grafana.namespace" . }} 7 | labels: 8 | app.kubernetes.io/name: {{ include "grafana.name" . }}-image-renderer 9 | helm.sh/chart: {{ include "grafana.chart" . }} 10 | app.kubernetes.io/managed-by: {{ .Release.Service }} 11 | app.kubernetes.io/instance: {{ .Release.Name }} 12 | spec: 13 | scaleTargetRef: 14 | apiVersion: apps/v1 15 | kind: Deployment 16 | name: {{ include "grafana.fullname" . }}-image-renderer 17 | minReplicas: {{ .Values.imageRenderer.autoscaling.minReplicas }} 18 | maxReplicas: {{ .Values.imageRenderer.autoscaling.maxReplicas }} 19 | metrics: 20 | {{- if .Values.imageRenderer.autoscaling.targetMemory }} 21 | - type: Resource 22 | resource: 23 | name: memory 24 | {{- if eq (include "grafana.hpa.apiVersion" .) "autoscaling/v2beta1" }} 25 | targetAverageUtilization: {{ .Values.imageRenderer.autoscaling.targetMemory }} 26 | {{- else }} 27 | target: 28 | type: Utilization 29 | averageUtilization: {{ .Values.imageRenderer.autoscaling.targetMemory }} 30 | {{- end }} 31 | {{- end }} 32 | {{- if .Values.imageRenderer.autoscaling.targetCPU }} 33 | - type: Resource 34 | resource: 35 | name: cpu 36 | {{- if eq (include "grafana.hpa.apiVersion" .) "autoscaling/v2beta1" }} 37 | targetAverageUtilization: {{ .Values.imageRenderer.autoscaling.targetCPU }} 38 | {{- else }} 39 | target: 40 | type: Utilization 41 | averageUtilization: {{ .Values.imageRenderer.autoscaling.targetCPU }} 42 | {{- end }} 43 | {{- end }} 44 | {{- if .Values.imageRenderer.autoscaling.behavior }} 45 | behavior: {{ toYaml .Values.imageRenderer.autoscaling.behavior | nindent 4 }} 46 | {{- end }} 47 | {{- end }} 48 | -------------------------------------------------------------------------------- /deployment/helm-charts/api-server/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "..fullname" . -}} 3 | {{- $svcPort := .Values.service.port -}} 4 | {{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} 5 | {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} 6 | {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} 7 | {{- end }} 8 | {{- end }} 9 | {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} 10 | apiVersion: networking.k8s.io/v1 11 | {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 12 | apiVersion: networking.k8s.io/v1beta1 13 | {{- else -}} 14 | apiVersion: extensions/v1beta1 15 | {{- end }} 16 | kind: Ingress 17 | metadata: 18 | name: {{ $fullName }} 19 | labels: 20 | {{- include "..labels" . | nindent 4 }} 21 | {{- with .Values.ingress.annotations }} 22 | annotations: 23 | {{- toYaml . | nindent 4 }} 24 | {{- end }} 25 | spec: 26 | {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} 27 | ingressClassName: {{ .Values.ingress.className }} 28 | {{- end }} 29 | {{- if .Values.ingress.tls }} 30 | tls: 31 | {{- range .Values.ingress.tls }} 32 | - hosts: 33 | {{- range .hosts }} 34 | - {{ . | quote }} 35 | {{- end }} 36 | secretName: {{ .secretName }} 37 | {{- end }} 38 | {{- end }} 39 | rules: 40 | {{- range .Values.ingress.hosts }} 41 | - host: {{ .host | quote }} 42 | http: 43 | paths: 44 | {{- range .paths }} 45 | - path: {{ .path }} 46 | {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} 47 | pathType: {{ .pathType }} 48 | {{- end }} 49 | backend: 50 | {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} 51 | service: 52 | name: {{ $fullName }} 53 | port: 54 | number: {{ $svcPort }} 55 | {{- else }} 56 | serviceName: {{ $fullName }} 57 | servicePort: {{ $svcPort }} 58 | {{- end }} 59 | {{- end }} 60 | {{- end }} 61 | {{- end }} 62 | -------------------------------------------------------------------------------- /api-server/middleware/auth.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023-2024 Omnissa, LLC. 3 | SPDX-License-Identifier: Apache-2.0 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package middleware 17 | 18 | import ( 19 | "errors" 20 | "log" 21 | "net/http" 22 | "net/url" 23 | 24 | "github.com/gin-gonic/gin" 25 | 26 | "collie-api-server/config" 27 | "collie-api-server/httputil" 28 | authSvc "collie-api-server/service/auth" 29 | ) 30 | 31 | var loginUrl string = config.Require("oauth.hostUrl") + "/collie/portal/login" 32 | 33 | func handleAuth(c *gin.Context) bool { 34 | token := c.GetHeader("Authorization") 35 | if token == "" { 36 | t, err := c.Request.Cookie("auth") 37 | if err != nil { 38 | //log.Printf("auth failed. token=%s, err=%s", token, err.Error()) 39 | return false 40 | } 41 | t2, err := url.QueryUnescape(t.Value) 42 | if err != nil { 43 | log.Printf("auth failed. token=%s, err=%s", t, err.Error()) 44 | return false 45 | } 46 | token = t2 47 | } 48 | authInfo, err := authSvc.Authenticate(token) 49 | 50 | if err != nil { 51 | log.Printf("auth failed. token=%s, err=%s", token, err.Error()) 52 | return false 53 | } 54 | c.Set("auth", authInfo) 55 | return true 56 | } 57 | 58 | func RedirectToLoginOnAuthFailure(c *gin.Context) { 59 | if handleAuth(c) { 60 | c.Next() 61 | } else { 62 | c.Redirect(http.StatusTemporaryRedirect, loginUrl) 63 | } 64 | } 65 | 66 | func Authenticate(c *gin.Context) { 67 | if handleAuth(c) { 68 | c.Next() 69 | } else { 70 | httputil.Abort(c, http.StatusUnauthorized, errors.New("Authorization failed")) 71 | } 72 | } 73 | 74 | func GetAuth(c *gin.Context) authSvc.AuthInfo { 75 | authInfo, exist := c.Get("auth") 76 | if !exist { 77 | // should never happen. Should be blocked by middleware 78 | panic("Missing auth info. Should be handled by middleware") 79 | } 80 | return authInfo.(authSvc.AuthInfo) 81 | } 82 | -------------------------------------------------------------------------------- /api-server/service/agent-template.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023-2024 Omnissa, LLC. 3 | SPDX-License-Identifier: Apache-2.0 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package service 17 | 18 | import ( 19 | "bytes" 20 | "collie-api-server/config" 21 | _ "embed" 22 | b64 "encoding/base64" 23 | "text/template" 24 | ) 25 | 26 | type AgentYamlParams struct { 27 | ApiKey string 28 | ApiUrl string 29 | EsUrl string 30 | EsKey string 31 | Provider string 32 | Image string 33 | AgentId string 34 | } 35 | 36 | var ( 37 | //go:embed template/agent.yaml 38 | templateAgentYaml string 39 | 40 | //go:embed template/kube-bench-job.yaml 41 | templateKubeBenchJob string 42 | 43 | //go:embed template/kube-hunter-job.yaml 44 | templateKubeHunterJob string 45 | ) 46 | 47 | func GenerageAgentYaml(provider string, apiKey string, esKey string, agentId string) (string, error) { 48 | 49 | cfg := config.Get() 50 | t, err := template.New("agent.yaml"). 51 | Option("missingkey=error"). 52 | Parse(templateAgentYaml) 53 | if err != nil { 54 | return "", err 55 | } 56 | 57 | data := AgentYamlParams{ 58 | ApiUrl: cfg.ApiURL, 59 | ApiKey: b64encode(apiKey), // secret, need encoding 60 | EsUrl: cfg.EsURL, 61 | EsKey: b64encode(esKey), // secret, need encoding 62 | Provider: provider, 63 | Image: cfg.AgentImage, 64 | AgentId: agentId, 65 | } 66 | 67 | var buffer bytes.Buffer 68 | err = t.Execute(&buffer, data) 69 | if err != nil { 70 | return "", err 71 | } 72 | text := buffer.String() 73 | 74 | text = combineK8sYaml(text, templateKubeBenchJob) 75 | text = combineK8sYaml(text, templateKubeHunterJob) 76 | 77 | return text, nil 78 | } 79 | 80 | func combineK8sYaml(src1 string, src2 string) string { 81 | return src1 + "\n---\n" + src2 82 | } 83 | 84 | func b64encode(val string) string { 85 | return b64.StdEncoding.EncodeToString([]byte(val)) 86 | } 87 | -------------------------------------------------------------------------------- /CONTRIBUTING_CLA.md: -------------------------------------------------------------------------------- 1 | # Contributing to compliance-dashboard-for-kubernetes 2 | 3 | We welcome contributions from the community and first want to thank you for taking the time to contribute! 4 | 5 | 6 | ## Ways to contribute 7 | 8 | We welcome many different types of contributions and not all of them need a Pull request. Contributions may include: 9 | 10 | * New features and proposals 11 | * Documentation 12 | * Bug fixes 13 | * Issue Triage 14 | * Answering questions and giving feedback 15 | * Helping to onboard new contributors 16 | * Other related activities 17 | 18 | ## Getting started 19 | 20 | ### Development Environment Setup 21 | WIP 22 | 23 | ### Build 24 | WIP 25 | 26 | ### Run and Test 27 | WIP 28 | 29 | ### Common Errors 30 | 31 | 32 | ## Contribution Flow 33 | 34 | This is a rough outline of what a contributor's workflow looks like: 35 | 36 | * Make a fork of the repository within your GitHub account 37 | * Create a topic branch in your fork from where you want to base your work 38 | * Make commits of logical units 39 | * Make sure your commit messages are with the proper format, quality and descriptiveness (see below) 40 | * Push your changes to the topic branch in your fork 41 | * Create a pull request containing that commit 42 | 43 | We follow the GitHub workflow and you can find more details on the [GitHub flow documentation](https://docs.github.com/en/get-started/quickstart/github-flow). 44 | 45 | 46 | ### Pull Request Checklist 47 | 48 | Before submitting your pull request, we advise you to use the following: 49 | 50 | 1. Check if your code changes will pass both code linting checks and unit tests. 51 | 2. Ensure your commit messages are descriptive. We follow the conventions on [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/). Be sure to include any related GitHub issue references in the commit message. See [GFM syntax](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown) for referencing issues and commits. 52 | 3. Check the commits and commits messages and ensure they are free from typos. 53 | 54 | ## Reporting Bugs and Creating Issues 55 | 56 | For specifics on what to include in your report, please follow the guidelines in the issue and pull request templates when available. 57 | 58 | 59 | ## Ask for Help 60 | 61 | The best way to reach us with a question when contributing is to ask on: 62 | 63 | * The original GitHub issue 64 | * The developer mailing list 65 | 66 | 67 | ## Additional Resources 68 | 69 | 70 | -------------------------------------------------------------------------------- /deployment.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Mac 4 | Install homebrew: https://brew.sh/ 5 | Install kubectl: https://formulae.brew.sh/formula/kubernetes-cli 6 | Install minikube: https://minikube.sigs.k8s.io/docs/start/ 7 | Install helm chart: https://helm.sh/docs/intro/quickstart/ 8 | 9 | Mac 10 | Install/upgrade kubectl: 11 | brew upgrade kubectl 12 | brew link --overwrite kubernetes-cli 13 | 14 | Install/upgrade minikube: 15 | brew unlink minikube 16 | brew install minikube 17 | brew link minikube 18 | 19 | Install/upgrade helm: 20 | brew install helm 21 | 22 | Update helm repo: 23 | helm repo add grafana https://grafana.github.io/helm-charts 24 | helm repo add elastic https://helm.elastic.co 25 | 26 | minikube addons enable default-storageclass 27 | minikube addons enable storage-provisioner 28 | 29 | Install ES 30 | helm install -n collie-server es elastic/elasticsearch -f ./es/values.yaml --wait 31 | 32 | Get ES password 33 | export ES_PASSWORD=$(kubectl get -n collie-server secret/elasticsearch-master-credentials -o jsonpath="{.data.password}" | base64 -d) 34 | ES_USER=$(kubectl get -n collie-server secret/elasticsearch-master-credentials -o jsonpath="{.data.username}" | base64 -d) 35 | ES_AUTH=$ES_USER:$ES_PASSWORD 36 | ES_URL=https://collie-dev.org:9200 37 | 38 | Create ES index 39 | ``` 40 | curl -k -u $ES_AUTH -X PUT -H "Content-Type: application/json" -d ' 41 | { 42 | "mappings": { 43 | "properties": { 44 | "resource": { 45 | "dynamic": false, 46 | "properties": { 47 | "metadata": { 48 | "properties": { 49 | "name": { 50 | "type": "text" 51 | }, 52 | "namespace": { 53 | "type": "text" 54 | } 55 | } 56 | } 57 | } 58 | } 59 | } 60 | } 61 | }' $ES_URL/collie-k8s-elastic 62 | ``` 63 | 64 | Install Grafana 65 | envsubst < ./grafana/values.yaml > tmp.yaml 66 | 67 | #helm install grafana grafana/grafana -f ./grafana/values.yaml -n collie-server --set "grafana.datasources.\"datasources.yaml\".datasources[0].secureJsonData.basicAuthPassword=$ES_PASSWORD" 68 | 69 | helm install grafana grafana/grafana -f tmp.yaml -n collie-server --wait 70 | rm tmp.yaml 71 | 72 | Forward grafana and ES 73 | kubectl port-forward -n collie-server --address 0.0.0.0 services/elasticsearch-master 9200:9200 & \ 74 | kubectl port-forward -n collie-server --address 0.0.0.0 services/grafana 3000:3000 & \ 75 | 76 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | {{- if (and (not .Values.useStatefulSet) (or (not .Values.persistence.enabled) (eq .Values.persistence.type "pvc"))) }} 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: {{ include "grafana.fullname" . }} 6 | namespace: {{ include "grafana.namespace" . }} 7 | labels: 8 | {{- include "grafana.labels" . | nindent 4 }} 9 | {{- with .Values.labels }} 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- with .Values.annotations }} 13 | annotations: 14 | {{- toYaml . | nindent 4 }} 15 | {{- end }} 16 | spec: 17 | {{- if and (not .Values.autoscaling.enabled) (.Values.replicas) }} 18 | replicas: {{ .Values.replicas }} 19 | {{- end }} 20 | revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} 21 | selector: 22 | matchLabels: 23 | {{- include "grafana.selectorLabels" . | nindent 6 }} 24 | {{- with .Values.deploymentStrategy }} 25 | strategy: 26 | {{- toYaml . | trim | nindent 4 }} 27 | {{- end }} 28 | template: 29 | metadata: 30 | labels: 31 | {{- include "grafana.selectorLabels" . | nindent 8 }} 32 | {{- with .Values.podLabels }} 33 | {{- toYaml . | nindent 8 }} 34 | {{- end }} 35 | annotations: 36 | checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} 37 | checksum/dashboards-json-config: {{ include (print $.Template.BasePath "/dashboards-json-configmap.yaml") . | sha256sum }} 38 | checksum/sc-dashboard-provider-config: {{ include (print $.Template.BasePath "/configmap-dashboard-provider.yaml") . | sha256sum }} 39 | {{- if and (or (and (not .Values.admin.existingSecret) (not .Values.env.GF_SECURITY_ADMIN_PASSWORD__FILE) (not .Values.env.GF_SECURITY_ADMIN_PASSWORD)) (and .Values.ldap.enabled (not .Values.ldap.existingSecret))) (not .Values.env.GF_SECURITY_DISABLE_INITIAL_ADMIN_CREATION) }} 40 | checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} 41 | {{- end }} 42 | {{- if .Values.envRenderSecret }} 43 | checksum/secret-env: {{ include (print $.Template.BasePath "/secret-env.yaml") . | sha256sum }} 44 | {{- end }} 45 | kubectl.kubernetes.io/default-container: {{ .Chart.Name }} 46 | {{- with .Values.podAnnotations }} 47 | {{- toYaml . | nindent 8 }} 48 | {{- end }} 49 | spec: 50 | {{- include "grafana.pod" . | nindent 6 }} 51 | {{- end }} 52 | -------------------------------------------------------------------------------- /api-server/service/oauth/common/common.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023-2024 Omnissa, LLC. 3 | SPDX-License-Identifier: Apache-2.0 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | package common 18 | 19 | import ( 20 | "collie-api-server/util" 21 | "context" 22 | b64 "encoding/base64" 23 | "fmt" 24 | "net/http" 25 | 26 | "golang.org/x/oauth2" 27 | lru "k8s.io/utils/lru" 28 | ) 29 | 30 | var ( 31 | stateLRU *lru.Cache = lru.New(1024) 32 | ) 33 | 34 | func init() { 35 | } 36 | 37 | func GetAuthUrl(oauthConfig *oauth2.Config) string { 38 | state := util.RandomString(8) 39 | codeVerifier := b64encode(util.RandomString(44)) 40 | stateLRU.Add(state, codeVerifier) 41 | 42 | //codeChallenge := oauth2.SetAuthURLParam("code_challenge", codeVerifier) 43 | //codeChallengeMethod := oauth2.SetAuthURLParam("code_challenge_method", "S256") 44 | 45 | //return oauthConfig.AuthCodeURL(state, codeChallenge, codeChallengeMethod) 46 | return oauthConfig.AuthCodeURL(state) 47 | } 48 | 49 | func b64encode(val string) string { 50 | var RawURLEncoding = b64.URLEncoding.WithPadding(b64.NoPadding) 51 | return RawURLEncoding.EncodeToString([]byte(val)) 52 | } 53 | 54 | func HandleCallback(oauthConfig *oauth2.Config, state string, code string) (*oauth2.Token, error, int) { 55 | 56 | _, present := stateLRU.Get(state) 57 | if !present { 58 | return nil, fmt.Errorf("Invalid state parameter: %s", state), http.StatusBadRequest 59 | } 60 | stateLRU.Remove(state) 61 | 62 | token, err := oauthConfig.Exchange(context.Background(), code) 63 | if err != nil { 64 | return nil, err, http.StatusInternalServerError 65 | } 66 | 67 | //log.Println("Callback received, exchange token...") 68 | //token, err := oauthConfig.Exchange(context.Background(), code, oauth2.SetAuthURLParam("code_verifier", codeVerifier.(string))) 69 | //if err != nil { 70 | // return nil, err, http.StatusInternalServerError 71 | //} 72 | //log.Println("Auth complete. Token received.") 73 | return token, nil, http.StatusOK 74 | } 75 | -------------------------------------------------------------------------------- /deployment/helm-charts/api-server/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "..fullname" . }} 5 | labels: 6 | {{- include "..labels" . | nindent 4 }} 7 | spec: 8 | {{- if not .Values.autoscaling.enabled }} 9 | replicas: {{ .Values.replicaCount }} 10 | {{- end }} 11 | selector: 12 | matchLabels: 13 | {{- include "..selectorLabels" . | nindent 6 }} 14 | template: 15 | metadata: 16 | {{- with .Values.podAnnotations }} 17 | annotations: 18 | {{- toYaml . | nindent 8 }} 19 | {{- end }} 20 | labels: 21 | {{- include "..selectorLabels" . | nindent 8 }} 22 | spec: 23 | {{- with .Values.imagePullSecrets }} 24 | imagePullSecrets: 25 | {{- toYaml . | nindent 8 }} 26 | {{- end }} 27 | serviceAccountName: {{ include "..serviceAccountName" . }} 28 | automountServiceAccountToken: true 29 | securityContext: 30 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 31 | containers: 32 | - name: {{ .Chart.Name }} 33 | securityContext: 34 | {{- toYaml .Values.securityContext | nindent 12 }} 35 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 36 | imagePullPolicy: {{ .Values.image.pullPolicy }} 37 | env: 38 | - name: PPROF_PORT 39 | value: "6060" 40 | envFrom: 41 | - configMapRef: 42 | name: {{ include "..fullname" . }} 43 | - secretRef: 44 | name: {{ include "..fullname" . }} 45 | - secretRef: 46 | name: elasticsearch-master-credentials 47 | ports: 48 | - name: healthz 49 | containerPort: 9876 50 | protocol: TCP 51 | - name: http 52 | containerPort: 8080 53 | protocol: TCP 54 | livenessProbe: 55 | httpGet: 56 | port: healthz 57 | readinessProbe: 58 | httpGet: 59 | port: healthz 60 | resources: 61 | {{- toYaml .Values.resources | nindent 12 }} 62 | {{- with .Values.nodeSelector }} 63 | nodeSelector: 64 | {{- toYaml . | nindent 8 }} 65 | {{- end }} 66 | {{- with .Values.affinity }} 67 | affinity: 68 | {{- toYaml . | nindent 8 }} 69 | {{- end }} 70 | {{- with .Values.tolerations }} 71 | tolerations: 72 | {{- toYaml . | nindent 8 }} 73 | {{- end }} 74 | -------------------------------------------------------------------------------- /dashboard/cert/nanw.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIGzzCCBbegAwIBAgIQCnYpIDwD8ksaMcV83/eQAzANBgkqhkiG9w0BAQsFADBP 3 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMSkwJwYDVQQDEyBE 4 | aWdpQ2VydCBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMTAeFw0yMjA5MTQwMDAwMDBa 5 | Fw0yMzEwMTUyMzU5NTlaMGsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9y 6 | bmlhMRIwEAYDVQQHEwlQYWxvIEFsdG8xFTATBgNVBAoTDFZNd2FyZSwgSW5jLjEc 7 | MBoGA1UEAxMTbmFudy5lbmcudm13YXJlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD 8 | ggEPADCCAQoCggEBAK6rgEo93BI3xBpebe5UAQDfJbRsjf5PnqMfOBxaXoL38xz4 9 | ZzaDSUyKbU07RAoZizofGlomRTAoIJUvUH+TjwgZm7bvTYO2M5Cn4+9zoqO4j54G 10 | kvVdzplatbo6eHwdozgMeSNFpa2UtYVttpfm84jLE1wE9edJseZ8jxhzdItAgsJa 11 | o8ScC7bX9X6pWL7QqK57XUgiSuCPSCgnTS48R6L8q84y6sCi8MNJkW9B7gC54buP 12 | RInlSqwUy69cHhMq7GpXkCrZziZGQLmsySbHbeMPYUm8gid3sbQsk+rqyJJdMCjD 13 | b6SB8Sgoiq4aw6n7nF00ygSVn0+1nalUwEHV/ScCAwEAAaOCA4kwggOFMB8GA1Ud 14 | IwQYMBaAFLdrouqoqoSMeeq02g+YssWVdrn0MB0GA1UdDgQWBBQJcJOKflvC77N6 15 | BnIEG/WUawFtDjA1BgNVHREELjAsghNuYW53LmVuZy52bXdhcmUuY29tghVjb2xs 16 | aWUuZW5nLnZtd2FyZS5jb20wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsG 17 | AQUFBwMBBggrBgEFBQcDAjCBjwYDVR0fBIGHMIGEMECgPqA8hjpodHRwOi8vY3Js 18 | My5kaWdpY2VydC5jb20vRGlnaUNlcnRUTFNSU0FTSEEyNTYyMDIwQ0ExLTQuY3Js 19 | MECgPqA8hjpodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRUTFNSU0FT 20 | SEEyNTYyMDIwQ0ExLTQuY3JsMD4GA1UdIAQ3MDUwMwYGZ4EMAQICMCkwJwYIKwYB 21 | BQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzB/BggrBgEFBQcBAQRz 22 | MHEwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBJBggrBgEF 23 | BQcwAoY9aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VExTUlNB 24 | U0hBMjU2MjAyMENBMS0xLmNydDAJBgNVHRMEAjAAMIIBfQYKKwYBBAHWeQIEAgSC 25 | AW0EggFpAWcAdQDoPtDaPvUGNTLnVyi8iWvJA9PL0RFr7Otp4Xd9bQa9bgAAAYM8 26 | 5d8TAAAEAwBGMEQCIDkuFqnuujXX5kBWNKGvEqF/fqUstx3R8Rgpow+URV5uAiAE 27 | stDDN242vZH3tw0vNnkT4AqcKX6CZj+NmbRuo1yURAB2ADXPGRu/sWxXvw+tTG1C 28 | y7u2JyAmUeo/4SrvqAPDO9ZMAAABgzzl3ukAAAQDAEcwRQIgKsMap3e2pwPGhW+G 29 | crGTR/pDhKSXX6fIXX3nhHaD8XcCIQC7ZgkuLbufzS4nEjqu6WxwIJD4LUW/Chii 30 | uPiDAG3kaAB2ALNzdwfhhFD4Y4bWBancEQlKeS2xZwwLh9zwAw55NqWaAAABgzzl 31 | 31wAAAQDAEcwRQIgaiKCxnJYzfpiQxsIeHt/t7pOxLABIbHRw7CLKRtDeeACIQDO 32 | uaZVe3FdhdM4BDQSMXrn+pI2G8SBeA3EUOexJZu/2jANBgkqhkiG9w0BAQsFAAOC 33 | AQEAW9CJSmfFRrZ++eCjXP9wfDzwlaGKBJmi3XdgDNnQqDIbdFh7nGOH44Hn9csH 34 | nGXNvmLqhqFd/sWxq9j/K3xOKd7XRdpiXczhqr5EvVg9tkeOD030XwNJMo3ZHLh2 35 | 4jK2Q+r9Zrs2ebsMY44zjS+F9I2T0EZ1cT/9/stIgy6zKPVjj3epvQr8obZ3+DMp 36 | DXSIdaXYN/UbDw7CsL7tRroeui6DCN73e2J1c6XYP8al+LQCfbl7J1xdc1oCnNOD 37 | K7KPY+zYpVHsTJa9ARVilgL7CfficQQgim1UlTwgqmp5vh6xCNmQ6474AEy50Ql4 38 | 2uzHZsPo7iTCu8RCfE+XpQCeaA== 39 | -----END CERTIFICATE----- 40 | -------------------------------------------------------------------------------- /dashboard/cert/nanw_eng_vmware_com.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIGzzCCBbegAwIBAgIQCnYpIDwD8ksaMcV83/eQAzANBgkqhkiG9w0BAQsFADBP 3 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMSkwJwYDVQQDEyBE 4 | aWdpQ2VydCBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMTAeFw0yMjA5MTQwMDAwMDBa 5 | Fw0yMzEwMTUyMzU5NTlaMGsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9y 6 | bmlhMRIwEAYDVQQHEwlQYWxvIEFsdG8xFTATBgNVBAoTDFZNd2FyZSwgSW5jLjEc 7 | MBoGA1UEAxMTbmFudy5lbmcudm13YXJlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD 8 | ggEPADCCAQoCggEBAK6rgEo93BI3xBpebe5UAQDfJbRsjf5PnqMfOBxaXoL38xz4 9 | ZzaDSUyKbU07RAoZizofGlomRTAoIJUvUH+TjwgZm7bvTYO2M5Cn4+9zoqO4j54G 10 | kvVdzplatbo6eHwdozgMeSNFpa2UtYVttpfm84jLE1wE9edJseZ8jxhzdItAgsJa 11 | o8ScC7bX9X6pWL7QqK57XUgiSuCPSCgnTS48R6L8q84y6sCi8MNJkW9B7gC54buP 12 | RInlSqwUy69cHhMq7GpXkCrZziZGQLmsySbHbeMPYUm8gid3sbQsk+rqyJJdMCjD 13 | b6SB8Sgoiq4aw6n7nF00ygSVn0+1nalUwEHV/ScCAwEAAaOCA4kwggOFMB8GA1Ud 14 | IwQYMBaAFLdrouqoqoSMeeq02g+YssWVdrn0MB0GA1UdDgQWBBQJcJOKflvC77N6 15 | BnIEG/WUawFtDjA1BgNVHREELjAsghNuYW53LmVuZy52bXdhcmUuY29tghVjb2xs 16 | aWUuZW5nLnZtd2FyZS5jb20wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsG 17 | AQUFBwMBBggrBgEFBQcDAjCBjwYDVR0fBIGHMIGEMECgPqA8hjpodHRwOi8vY3Js 18 | My5kaWdpY2VydC5jb20vRGlnaUNlcnRUTFNSU0FTSEEyNTYyMDIwQ0ExLTQuY3Js 19 | MECgPqA8hjpodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRUTFNSU0FT 20 | SEEyNTYyMDIwQ0ExLTQuY3JsMD4GA1UdIAQ3MDUwMwYGZ4EMAQICMCkwJwYIKwYB 21 | BQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzB/BggrBgEFBQcBAQRz 22 | MHEwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBJBggrBgEF 23 | BQcwAoY9aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VExTUlNB 24 | U0hBMjU2MjAyMENBMS0xLmNydDAJBgNVHRMEAjAAMIIBfQYKKwYBBAHWeQIEAgSC 25 | AW0EggFpAWcAdQDoPtDaPvUGNTLnVyi8iWvJA9PL0RFr7Otp4Xd9bQa9bgAAAYM8 26 | 5d8TAAAEAwBGMEQCIDkuFqnuujXX5kBWNKGvEqF/fqUstx3R8Rgpow+URV5uAiAE 27 | stDDN242vZH3tw0vNnkT4AqcKX6CZj+NmbRuo1yURAB2ADXPGRu/sWxXvw+tTG1C 28 | y7u2JyAmUeo/4SrvqAPDO9ZMAAABgzzl3ukAAAQDAEcwRQIgKsMap3e2pwPGhW+G 29 | crGTR/pDhKSXX6fIXX3nhHaD8XcCIQC7ZgkuLbufzS4nEjqu6WxwIJD4LUW/Chii 30 | uPiDAG3kaAB2ALNzdwfhhFD4Y4bWBancEQlKeS2xZwwLh9zwAw55NqWaAAABgzzl 31 | 31wAAAQDAEcwRQIgaiKCxnJYzfpiQxsIeHt/t7pOxLABIbHRw7CLKRtDeeACIQDO 32 | uaZVe3FdhdM4BDQSMXrn+pI2G8SBeA3EUOexJZu/2jANBgkqhkiG9w0BAQsFAAOC 33 | AQEAW9CJSmfFRrZ++eCjXP9wfDzwlaGKBJmi3XdgDNnQqDIbdFh7nGOH44Hn9csH 34 | nGXNvmLqhqFd/sWxq9j/K3xOKd7XRdpiXczhqr5EvVg9tkeOD030XwNJMo3ZHLh2 35 | 4jK2Q+r9Zrs2ebsMY44zjS+F9I2T0EZ1cT/9/stIgy6zKPVjj3epvQr8obZ3+DMp 36 | DXSIdaXYN/UbDw7CsL7tRroeui6DCN73e2J1c6XYP8al+LQCfbl7J1xdc1oCnNOD 37 | K7KPY+zYpVHsTJa9ARVilgL7CfficQQgim1UlTwgqmp5vh6xCNmQ6474AEy50Ql4 38 | 2uzHZsPo7iTCu8RCfE+XpQCeaA== 39 | -----END CERTIFICATE----- 40 | -------------------------------------------------------------------------------- /api-server/commonms/base.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023-2024 Omnissa, LLC. 3 | SPDX-License-Identifier: Apache-2.0 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package commonms 17 | 18 | import ( 19 | "collie-api-server/config" 20 | "context" 21 | "errors" 22 | "github.com/sirupsen/logrus" 23 | "runtime/debug" 24 | "sigs.k8s.io/controller-runtime/pkg/manager/signals" 25 | ) 26 | 27 | func RunApp(fnRun func(config.Config, *logrus.Entry, context.Context, chan error) error) { 28 | 29 | cfg := config.Get() 30 | 31 | logger := logrus.New() 32 | logger.SetLevel(logrus.Level(cfg.Log.Level)) 33 | log := logger.WithField("version", "local") 34 | 35 | logBuildInfo(log) 36 | 37 | log.Printf("%v", cfg) 38 | 39 | ctx := signals.SetupSignalHandler() 40 | ctx, ctxCancel := context.WithCancel(ctx) 41 | defer ctxCancel() 42 | 43 | exitCh := make(chan error, 10) 44 | go watchExitErrors(ctx, log, exitCh, ctxCancel) 45 | closeHealthz := StartHealthz(cfg, log, exitCh) 46 | defer closeHealthz() 47 | 48 | if err := fnRun(cfg, log, ctx, exitCh); err != nil { 49 | log.Fatalf("agent failed: %v", err) 50 | } 51 | 52 | log.Println("Exit") 53 | } 54 | 55 | func logBuildInfo(log *logrus.Entry) { 56 | // - vcs.revision: the revision identifier for the current commit or checkout 57 | // - vcs.time: the modification time associated with vcs.revision, in RFC3339 format 58 | interestedFields := map[string]int{"vcs.revision": 1, "vcs.time": 1} 59 | if bi, ok := debug.ReadBuildInfo(); ok { 60 | log.Printf(bi.GoVersion) 61 | for _, v := range bi.Settings { 62 | if _, ok := interestedFields[v.Key]; ok { 63 | log.Println(v.Key, v.Value) 64 | } 65 | } 66 | } 67 | } 68 | 69 | // if any errors are observed on exitCh, context cancel is called, and all errors in the channel are logged 70 | func watchExitErrors(ctx context.Context, log *logrus.Entry, exitCh chan error, ctxCancel func()) { 71 | select { 72 | case err := <-exitCh: 73 | if err != nil && !errors.Is(err, context.Canceled) { 74 | log.Errorf("Stopped with an error: %v", err) 75 | } 76 | ctxCancel() 77 | case <-ctx.Done(): 78 | return 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/statefulset.yaml: -------------------------------------------------------------------------------- 1 | {{- $sts := list "sts" "StatefulSet" "statefulset" -}} 2 | {{- if (or (.Values.useStatefulSet) (and .Values.persistence.enabled (not .Values.persistence.existingClaim) (has .Values.persistence.type $sts)))}} 3 | apiVersion: apps/v1 4 | kind: StatefulSet 5 | metadata: 6 | name: {{ include "grafana.fullname" . }} 7 | namespace: {{ include "grafana.namespace" . }} 8 | labels: 9 | {{- include "grafana.labels" . | nindent 4 }} 10 | {{- with .Values.annotations }} 11 | annotations: 12 | {{- toYaml . | nindent 4 }} 13 | {{- end }} 14 | spec: 15 | replicas: {{ .Values.replicas }} 16 | selector: 17 | matchLabels: 18 | {{- include "grafana.selectorLabels" . | nindent 6 }} 19 | serviceName: {{ include "grafana.fullname" . }}-headless 20 | template: 21 | metadata: 22 | labels: 23 | {{- include "grafana.selectorLabels" . | nindent 8 }} 24 | {{- with .Values.podLabels }} 25 | {{- toYaml . | nindent 8 }} 26 | {{- end }} 27 | annotations: 28 | checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} 29 | checksum/dashboards-json-config: {{ include (print $.Template.BasePath "/dashboards-json-configmap.yaml") . | sha256sum }} 30 | checksum/sc-dashboard-provider-config: {{ include (print $.Template.BasePath "/configmap-dashboard-provider.yaml") . | sha256sum }} 31 | {{- if and (or (and (not .Values.admin.existingSecret) (not .Values.env.GF_SECURITY_ADMIN_PASSWORD__FILE) (not .Values.env.GF_SECURITY_ADMIN_PASSWORD)) (and .Values.ldap.enabled (not .Values.ldap.existingSecret))) (not .Values.env.GF_SECURITY_DISABLE_INITIAL_ADMIN_CREATION) }} 32 | checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} 33 | {{- end }} 34 | kubectl.kubernetes.io/default-container: {{ .Chart.Name }} 35 | {{- with .Values.podAnnotations }} 36 | {{- toYaml . | nindent 8 }} 37 | {{- end }} 38 | spec: 39 | {{- include "grafana.pod" . | nindent 6 }} 40 | {{- if .Values.persistence.enabled}} 41 | volumeClaimTemplates: 42 | - metadata: 43 | name: storage 44 | spec: 45 | accessModes: {{ .Values.persistence.accessModes }} 46 | storageClassName: {{ .Values.persistence.storageClassName }} 47 | resources: 48 | requests: 49 | storage: {{ .Values.persistence.size }} 50 | {{- with .Values.persistence.selectorLabels }} 51 | selector: 52 | matchLabels: 53 | {{- toYaml . | nindent 10 }} 54 | {{- end }} 55 | {{- end }} 56 | {{- end }} 57 | -------------------------------------------------------------------------------- /deployment/scripts/redeploy-api-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source .env 4 | 5 | if [[ "$OSTYPE" == "linux-gnu"* ]]; then 6 | B64CMD="base64 --wrap=0" 7 | elif [[ "$OSTYPE" == "darwin"* ]]; then 8 | B64CMD="base64" 9 | elif [[ "$OSTYPE" == "cygwin" ]]; then 10 | echo "TODO: OSTYPE cygwin" 11 | exit 1 12 | elif [[ "$OSTYPE" == "msys" ]]; then 13 | echo "TODO: OSTYPE msys" 14 | exit 1 15 | elif [[ "$OSTYPE" == "win32" ]]; then 16 | echo "TODO: OSTYPE win32" 17 | exit 1 18 | elif [[ "$OSTYPE" == "freebsd"* ]]; then 19 | echo "TODO: OSTYPE freebsd" 20 | exit 1 21 | else 22 | echo "TODO: unknown OSTYPE "$OSTYPE 23 | exit 1 24 | fi 25 | 26 | # Delete deployments 27 | kubectl delete namespace collie-server collie-agent --wait=true 28 | sleep 30 29 | 30 | minikube image rm collie.azurecr.io/collie-api-server:1 31 | minikube image rm collie.azurecr.io/collie-agent:1 32 | 33 | # clear data 34 | source es-recreate-index.sh 35 | 36 | # redeploy 37 | export ES_KEY_B64=$(echo -n $ES_KEY | base64 --wrap=0) 38 | export OAUTH_CSP_CLIENTID_B64=$(echo -n $OAUTH_CSP_CLIENTID | $B64CMD) 39 | export OAUTH_CSP_CLIENTSECRET_B64=$(echo -n $OAUTH_CSP_CLIENTSECRET | $B64CMD) 40 | export OAUTH_GITLAB_CLIENTID_B64=$(echo -n $OAUTH_GITLAB_CLIENTID | $B64CMD) 41 | export OAUTH_GITLAB_CLIENTSECRET_B64=$(echo -n $OAUTH_GITLAB_CLIENTSECRET | $B64CMD) 42 | 43 | envsubst < api-server.yaml | kubectl apply -f - 44 | kubectl wait deployment -n collie-server api-server --for condition=Available=True --timeout=90s 45 | AUTH_TOKEN=gitlab/$(source auth-gitlab.sh | jq -r '.access_token') 46 | sleep 10 47 | BOOTSTRAP_CMD=$(curl -skH "Authorization: $AUTH_TOKEN" https://collie.eng.omnissa.com/collie/api/v1/onboarding/bootstrap | jq -r ".cmd") 48 | 49 | AGENT_ID=$(echo $BOOTSTRAP_CMD | sed -n 's/.*aid\=\(.*\)\".*/\1/p') 50 | 51 | echo -e '#!/bin/bash\n' > deploy-agent.sh 52 | echo $BOOTSTRAP_CMD >> deploy-agent.sh 53 | chmod +x deploy-agent.sh 54 | 55 | source ./deploy-agent.sh 56 | sleep 30 57 | kubectl -n collie-agent logs deployment/agent 58 | 59 | # wait for data appear in ES 60 | 61 | echo $RESP 62 | 63 | for i in 1 2 64 | do 65 | RESP=$(curl -skS -u $COLLIE_ES_USERNAME:$COLLIE_ES_PASSWORD "$COLLIE_ES_URL/collie-k8s-elastic/_search?q=a:$AGENT_ID" | jq ".hits.hits[0]._source") 66 | 67 | if [ "$RESP" = "null" ]; then 68 | echo "Not ready yet..." 69 | sleep 10 70 | else 71 | echo "$RESP" 72 | echo "OK" 73 | kubectl delete namespace collie-agent --wait=true 74 | exit 0 75 | fi 76 | done 77 | 78 | echo "Failed waiting for agent report in ES" 79 | exit 1 80 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/image-renderer-network-policy.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.imageRenderer.enabled .Values.imageRenderer.networkPolicy.limitIngress }} 2 | --- 3 | apiVersion: networking.k8s.io/v1 4 | kind: NetworkPolicy 5 | metadata: 6 | name: {{ include "grafana.fullname" . }}-image-renderer-ingress 7 | namespace: {{ include "grafana.namespace" . }} 8 | annotations: 9 | comment: Limit image-renderer ingress traffic from grafana 10 | spec: 11 | podSelector: 12 | matchLabels: 13 | {{- include "grafana.imageRenderer.selectorLabels" . | nindent 6 }} 14 | {{- with .Values.imageRenderer.podLabels }} 15 | {{- toYaml . | nindent 6 }} 16 | {{- end }} 17 | 18 | policyTypes: 19 | - Ingress 20 | ingress: 21 | - ports: 22 | - port: {{ .Values.imageRenderer.service.targetPort }} 23 | protocol: TCP 24 | from: 25 | - namespaceSelector: 26 | matchLabels: 27 | kubernetes.io/metadata.name: {{ include "grafana.namespace" . }} 28 | podSelector: 29 | matchLabels: 30 | {{- include "grafana.selectorLabels" . | nindent 14 }} 31 | {{- with .Values.podLabels }} 32 | {{- toYaml . | nindent 14 }} 33 | {{- end }} 34 | {{- with .Values.imageRenderer.networkPolicy.extraIngressSelectors -}} 35 | {{ toYaml . | nindent 8 }} 36 | {{- end }} 37 | {{- end }} 38 | 39 | {{- if and .Values.imageRenderer.enabled .Values.imageRenderer.networkPolicy.limitEgress }} 40 | --- 41 | apiVersion: networking.k8s.io/v1 42 | kind: NetworkPolicy 43 | metadata: 44 | name: {{ include "grafana.fullname" . }}-image-renderer-egress 45 | namespace: {{ include "grafana.namespace" . }} 46 | annotations: 47 | comment: Limit image-renderer egress traffic to grafana 48 | spec: 49 | podSelector: 50 | matchLabels: 51 | {{- include "grafana.imageRenderer.selectorLabels" . | nindent 6 }} 52 | {{- with .Values.imageRenderer.podLabels }} 53 | {{- toYaml . | nindent 6 }} 54 | {{- end }} 55 | 56 | policyTypes: 57 | - Egress 58 | egress: 59 | # allow dns resolution 60 | - ports: 61 | - port: 53 62 | protocol: UDP 63 | - port: 53 64 | protocol: TCP 65 | # talk only to grafana 66 | - ports: 67 | - port: {{ .Values.service.targetPort }} 68 | protocol: TCP 69 | to: 70 | - namespaceSelector: 71 | matchLabels: 72 | name: {{ include "grafana.namespace" . }} 73 | podSelector: 74 | matchLabels: 75 | {{- include "grafana.selectorLabels" . | nindent 14 }} 76 | {{- with .Values.podLabels }} 77 | {{- toYaml . | nindent 14 }} 78 | {{- end }} 79 | {{- end }} 80 | -------------------------------------------------------------------------------- /k8s-agent/internal/commonms/base.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023-2024 Omnissa, LLC. 3 | SPDX-License-Identifier: Apache-2.0 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package commonms 17 | 18 | import ( 19 | "collie-agent/internal/config" 20 | "context" 21 | "encoding/json" 22 | "errors" 23 | "runtime/debug" 24 | 25 | "github.com/sirupsen/logrus" 26 | "sigs.k8s.io/controller-runtime/pkg/manager/signals" 27 | ) 28 | 29 | func RunApp(fnRun func(config.Config, *logrus.Entry, context.Context, chan error) error) { 30 | 31 | cfg := config.Get() 32 | 33 | logger := logrus.New() 34 | logger.SetLevel(logrus.Level(cfg.Log.Level)) 35 | log := logger.WithField("version", "local") 36 | 37 | logBuildInfo(log) 38 | 39 | bytes, err := json.MarshalIndent(cfg, "", " ") 40 | if err != nil { 41 | log.Warnf("Error serializing config to json: %s", err) 42 | } else { 43 | log.Println("config:", string(bytes)) 44 | } 45 | 46 | ctx := signals.SetupSignalHandler() 47 | ctx, ctxCancel := context.WithCancel(ctx) 48 | defer ctxCancel() 49 | 50 | exitCh := make(chan error, 10) 51 | go watchExitErrors(ctx, log, exitCh, ctxCancel) 52 | closeHealthz := StartHealthz(cfg, log, exitCh) 53 | defer closeHealthz() 54 | 55 | if err := fnRun(cfg, log, ctx, exitCh); err != nil { 56 | log.Fatalf("agent failed: %v", err) 57 | } 58 | 59 | log.Println("Exit") 60 | } 61 | 62 | func logBuildInfo(log *logrus.Entry) { 63 | // - vcs.revision: the revision identifier for the current commit or checkout 64 | // - vcs.time: the modification time associated with vcs.revision, in RFC3339 format 65 | interestedFields := map[string]int{"vcs.revision": 1, "vcs.time": 1} 66 | if bi, ok := debug.ReadBuildInfo(); ok { 67 | log.Printf(bi.GoVersion) 68 | for _, v := range bi.Settings { 69 | if _, ok := interestedFields[v.Key]; ok { 70 | log.Println(v.Key, v.Value) 71 | } 72 | } 73 | } 74 | } 75 | 76 | // if any errors are observed on exitCh, context cancel is called, and all errors in the channel are logged 77 | func watchExitErrors(ctx context.Context, log *logrus.Entry, exitCh chan error, ctxCancel func()) { 78 | select { 79 | case err := <-exitCh: 80 | if err != nil && !errors.Is(err, context.Canceled) { 81 | log.Errorf("Stopped with an error: %v", err) 82 | } 83 | ctxCancel() 84 | case <-ctx.Done(): 85 | return 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $ingressApiIsStable := eq (include "grafana.ingress.isStable" .) "true" -}} 3 | {{- $ingressSupportsIngressClassName := eq (include "grafana.ingress.supportsIngressClassName" .) "true" -}} 4 | {{- $ingressSupportsPathType := eq (include "grafana.ingress.supportsPathType" .) "true" -}} 5 | {{- $fullName := include "grafana.fullname" . -}} 6 | {{- $servicePort := .Values.service.port -}} 7 | {{- $ingressPath := .Values.ingress.path -}} 8 | {{- $ingressPathType := .Values.ingress.pathType -}} 9 | {{- $extraPaths := .Values.ingress.extraPaths -}} 10 | apiVersion: {{ include "grafana.ingress.apiVersion" . }} 11 | kind: Ingress 12 | metadata: 13 | name: {{ $fullName }} 14 | namespace: {{ include "grafana.namespace" . }} 15 | labels: 16 | {{- include "grafana.labels" . | nindent 4 }} 17 | {{- with .Values.ingress.labels }} 18 | {{- toYaml . | nindent 4 }} 19 | {{- end }} 20 | {{- with .Values.ingress.annotations }} 21 | annotations: 22 | {{- range $key, $value := . }} 23 | {{ $key }}: {{ tpl $value $ | quote }} 24 | {{- end }} 25 | {{- end }} 26 | spec: 27 | {{- if and $ingressSupportsIngressClassName .Values.ingress.ingressClassName }} 28 | ingressClassName: {{ .Values.ingress.ingressClassName }} 29 | {{- end -}} 30 | {{- with .Values.ingress.tls }} 31 | tls: 32 | {{- tpl (toYaml .) $ | nindent 4 }} 33 | {{- end }} 34 | rules: 35 | {{- if .Values.ingress.hosts }} 36 | {{- range .Values.ingress.hosts }} 37 | - host: {{ tpl . $ }} 38 | http: 39 | paths: 40 | {{- with $extraPaths }} 41 | {{- toYaml . | nindent 10 }} 42 | {{- end }} 43 | - path: {{ $ingressPath }} 44 | {{- if $ingressSupportsPathType }} 45 | pathType: {{ $ingressPathType }} 46 | {{- end }} 47 | backend: 48 | {{- if $ingressApiIsStable }} 49 | service: 50 | name: {{ $fullName }} 51 | port: 52 | number: {{ $servicePort }} 53 | {{- else }} 54 | serviceName: {{ $fullName }} 55 | servicePort: {{ $servicePort }} 56 | {{- end }} 57 | {{- end }} 58 | {{- else }} 59 | - http: 60 | paths: 61 | - backend: 62 | {{- if $ingressApiIsStable }} 63 | service: 64 | name: {{ $fullName }} 65 | port: 66 | number: {{ $servicePort }} 67 | {{- else }} 68 | serviceName: {{ $fullName }} 69 | servicePort: {{ $servicePort }} 70 | {{- end }} 71 | {{- with $ingressPath }} 72 | path: {{ . }} 73 | {{- end }} 74 | {{- if $ingressSupportsPathType }} 75 | pathType: {{ $ingressPathType }} 76 | {{- end }} 77 | {{- end -}} 78 | {{- end }} 79 | -------------------------------------------------------------------------------- /deployment/helm-charts/api-server/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for .. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | 7 | image: 8 | repository: collie.azurecr.io/collie-api-server 9 | pullPolicy: IfNotPresent 10 | # Overrides the image tag whose default is the chart appVersion. 11 | tag: 1 12 | 13 | imagePullSecrets: [] 14 | nameOverride: "" 15 | fullnameOverride: "" 16 | 17 | serviceAccount: 18 | # Specifies whether a service account should be created 19 | create: true 20 | # Annotations to add to the service account 21 | annotations: {} 22 | # The name of the service account to use. 23 | # If not set and create is true, a name is generated using the fullname template 24 | name: api-server 25 | 26 | podAnnotations: {} 27 | 28 | podSecurityContext: 29 | fsGroup: 1002 30 | runAsGroup: 1002 31 | runAsNonRoot: true 32 | runAsUser: 1002 33 | seccompProfile: 34 | type: RuntimeDefault 35 | 36 | securityContext: 37 | capabilities: 38 | drop: 39 | - ALL 40 | readOnlyRootFilesystem: true 41 | runAsNonRoot: true 42 | runAsUser: 1000 43 | allowPrivilegeEscalation: false 44 | 45 | 46 | service: 47 | type: ClusterIP 48 | port: 8080 49 | 50 | ingress: 51 | enabled: true 52 | className: "nginx" 53 | annotations: {} 54 | # kubernetes.io/ingress.class: nginx 55 | # kubernetes.io/tls-acme: "true" 56 | hosts: 57 | - host: collie-dev.org 58 | paths: 59 | - path: /collie 60 | pathType: Prefix 61 | - path: /swagger 62 | pathType: Prefix 63 | tls: [] 64 | # - secretName: chart-example-tls 65 | # hosts: 66 | # - chart-example.local 67 | 68 | resources: 69 | # We usually recommend not to specify default resources and to leave this as a conscious 70 | # choice for the user. This also increases chances charts run on environments with little 71 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 72 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 73 | limits: 74 | cpu: 100m 75 | memory: 128Mi 76 | # requests: 77 | # cpu: 100m 78 | # memory: 128Mi 79 | 80 | autoscaling: 81 | enabled: false 82 | minReplicas: 1 83 | maxReplicas: 100 84 | targetCPUUtilizationPercentage: 80 85 | # targetMemoryUtilizationPercentage: 80 86 | 87 | nodeSelector: {} 88 | 89 | tolerations: [] 90 | 91 | affinity: {} 92 | 93 | collie: 94 | url: http://collie-dev.org:8080 95 | 96 | es: 97 | #key: "elastic:asdfasdf" 98 | url: https://collie-dev.org:9200 99 | 100 | grafana: 101 | url: http://collie-dev.org:3000/d/qIbLYbT4z/k8s-compliance-report" 102 | 103 | oauth: 104 | csp_clientid: 105 | csp_clientsecret: 106 | gitlab_clientid: 107 | gitlab_clientsecret: 108 | google_clientid: 109 | google_clientsecret: 110 | -------------------------------------------------------------------------------- /deployment/scripts/job.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: batch/v1 3 | kind: Job 4 | metadata: 5 | name: kube-bench 6 | namespace: collie-agent 7 | spec: 8 | template: 9 | metadata: 10 | labels: 11 | app: kube-bench 12 | spec: 13 | hostPID: true 14 | containers: 15 | - name: kube-bench 16 | image: collie.azurecr.io/kube-bench:1 17 | command: ["kube-bench"] 18 | volumeMounts: 19 | - name: var-lib-etcd 20 | mountPath: /var/lib/etcd 21 | readOnly: true 22 | - name: var-lib-kubelet 23 | mountPath: /var/lib/kubelet 24 | readOnly: true 25 | - name: var-lib-kube-scheduler 26 | mountPath: /var/lib/kube-scheduler 27 | readOnly: true 28 | - name: var-lib-kube-controller-manager 29 | mountPath: /var/lib/kube-controller-manager 30 | readOnly: true 31 | - name: etc-systemd 32 | mountPath: /etc/systemd 33 | readOnly: true 34 | - name: lib-systemd 35 | mountPath: /lib/systemd/ 36 | readOnly: true 37 | - name: srv-kubernetes 38 | mountPath: /srv/kubernetes/ 39 | readOnly: true 40 | - name: etc-kubernetes 41 | mountPath: /etc/kubernetes 42 | readOnly: true 43 | # /usr/local/mount-from-host/bin is mounted to access kubectl / kubelet, for auto-detecting the Kubernetes version. 44 | # You can omit this mount if you specify --version as part of the command. 45 | - name: usr-bin 46 | mountPath: /usr/local/mount-from-host/bin 47 | readOnly: true 48 | - name: etc-cni-netd 49 | mountPath: /etc/cni/net.d/ 50 | readOnly: true 51 | - name: opt-cni-bin 52 | mountPath: /opt/cni/bin/ 53 | readOnly: true 54 | restartPolicy: Never 55 | volumes: 56 | - name: var-lib-etcd 57 | hostPath: 58 | path: "/var/lib/etcd" 59 | - name: var-lib-kubelet 60 | hostPath: 61 | path: "/var/lib/kubelet" 62 | - name: var-lib-kube-scheduler 63 | hostPath: 64 | path: "/var/lib/kube-scheduler" 65 | - name: var-lib-kube-controller-manager 66 | hostPath: 67 | path: "/var/lib/kube-controller-manager" 68 | - name: etc-systemd 69 | hostPath: 70 | path: "/etc/systemd" 71 | - name: lib-systemd 72 | hostPath: 73 | path: "/lib/systemd" 74 | - name: srv-kubernetes 75 | hostPath: 76 | path: "/srv/kubernetes" 77 | - name: etc-kubernetes 78 | hostPath: 79 | path: "/etc/kubernetes" 80 | - name: usr-bin 81 | hostPath: 82 | path: "/usr/bin" 83 | - name: etc-cni-netd 84 | hostPath: 85 | path: "/etc/cni/net.d/" 86 | - name: opt-cni-bin 87 | hostPath: 88 | path: "/opt/cni/bin/" 89 | -------------------------------------------------------------------------------- /api-server/service/template/kube-bench-job.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: kube-bench 5 | namespace: collie-agent 6 | spec: 7 | template: 8 | metadata: 9 | labels: 10 | app: kube-bench 11 | spec: 12 | hostPID: true 13 | containers: 14 | - name: kube-bench 15 | image: collie.azurecr.io/kube-bench:1 16 | command: ["kube-bench"] 17 | volumeMounts: 18 | - name: var-lib-etcd 19 | mountPath: /var/lib/etcd 20 | readOnly: true 21 | - name: var-lib-kubelet 22 | mountPath: /var/lib/kubelet 23 | readOnly: true 24 | - name: var-lib-kube-scheduler 25 | mountPath: /var/lib/kube-scheduler 26 | readOnly: true 27 | - name: var-lib-kube-controller-manager 28 | mountPath: /var/lib/kube-controller-manager 29 | readOnly: true 30 | - name: etc-systemd 31 | mountPath: /etc/systemd 32 | readOnly: true 33 | - name: lib-systemd 34 | mountPath: /lib/systemd/ 35 | readOnly: true 36 | - name: srv-kubernetes 37 | mountPath: /srv/kubernetes/ 38 | readOnly: true 39 | - name: etc-kubernetes 40 | mountPath: /etc/kubernetes 41 | readOnly: true 42 | # /usr/local/mount-from-host/bin is mounted to access kubectl / kubelet, for auto-detecting the Kubernetes version. 43 | # You can omit this mount if you specify --version as part of the command. 44 | - name: usr-bin 45 | mountPath: /usr/local/mount-from-host/bin 46 | readOnly: true 47 | - name: etc-cni-netd 48 | mountPath: /etc/cni/net.d/ 49 | readOnly: true 50 | - name: opt-cni-bin 51 | mountPath: /opt/cni/bin/ 52 | readOnly: true 53 | restartPolicy: Never 54 | volumes: 55 | - name: var-lib-etcd 56 | hostPath: 57 | path: "/var/lib/etcd" 58 | - name: var-lib-kubelet 59 | hostPath: 60 | path: "/var/lib/kubelet" 61 | - name: var-lib-kube-scheduler 62 | hostPath: 63 | path: "/var/lib/kube-scheduler" 64 | - name: var-lib-kube-controller-manager 65 | hostPath: 66 | path: "/var/lib/kube-controller-manager" 67 | - name: etc-systemd 68 | hostPath: 69 | path: "/etc/systemd" 70 | - name: lib-systemd 71 | hostPath: 72 | path: "/lib/systemd" 73 | - name: srv-kubernetes 74 | hostPath: 75 | path: "/srv/kubernetes" 76 | - name: etc-kubernetes 77 | hostPath: 78 | path: "/etc/kubernetes" 79 | - name: usr-bin 80 | hostPath: 81 | path: "/usr/bin" 82 | - name: etc-cni-netd 83 | hostPath: 84 | path: "/etc/cni/net.d/" 85 | - name: opt-cni-bin 86 | hostPath: 87 | path: "/opt/cni/bin/" -------------------------------------------------------------------------------- /deployment/helm-charts/grafana/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get your '{{ .Values.adminUser }}' user password by running: 2 | 3 | kubectl get secret --namespace {{ include "grafana.namespace" . }} {{ .Values.admin.existingSecret | default (include "grafana.fullname" .) }} -o jsonpath="{.data.{{ .Values.admin.passwordKey | default "admin-password" }}}" | base64 --decode ; echo 4 | 5 | 6 | 2. The Grafana server can be accessed via port {{ .Values.service.port }} on the following DNS name from within your cluster: 7 | 8 | {{ include "grafana.fullname" . }}.{{ include "grafana.namespace" . }}.svc.cluster.local 9 | {{ if .Values.ingress.enabled }} 10 | If you bind grafana to 80, please update values in values.yaml and reinstall: 11 | ``` 12 | securityContext: 13 | runAsUser: 0 14 | runAsGroup: 0 15 | fsGroup: 0 16 | 17 | command: 18 | - "setcap" 19 | - "'cap_net_bind_service=+ep'" 20 | - "/usr/sbin/grafana-server &&" 21 | - "sh" 22 | - "/run.sh" 23 | ``` 24 | Details refer to https://grafana.com/docs/installation/configuration/#http-port. 25 | Or grafana would always crash. 26 | 27 | From outside the cluster, the server URL(s) are: 28 | {{- range .Values.ingress.hosts }} 29 | http://{{ . }} 30 | {{- end }} 31 | {{- else }} 32 | Get the Grafana URL to visit by running these commands in the same shell: 33 | {{- if contains "NodePort" .Values.service.type }} 34 | export NODE_PORT=$(kubectl get --namespace {{ include "grafana.namespace" . }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "grafana.fullname" . }}) 35 | export NODE_IP=$(kubectl get nodes --namespace {{ include "grafana.namespace" . }} -o jsonpath="{.items[0].status.addresses[0].address}") 36 | echo http://$NODE_IP:$NODE_PORT 37 | {{- else if contains "LoadBalancer" .Values.service.type }} 38 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 39 | You can watch the status of by running 'kubectl get svc --namespace {{ include "grafana.namespace" . }} -w {{ include "grafana.fullname" . }}' 40 | export SERVICE_IP=$(kubectl get svc --namespace {{ include "grafana.namespace" . }} {{ include "grafana.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') 41 | http://$SERVICE_IP:{{ .Values.service.port -}} 42 | {{- else if contains "ClusterIP" .Values.service.type }} 43 | export POD_NAME=$(kubectl get pods --namespace {{ include "grafana.namespace" . }} -l "app.kubernetes.io/name={{ include "grafana.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 44 | kubectl --namespace {{ include "grafana.namespace" . }} port-forward $POD_NAME 3000 45 | {{- end }} 46 | {{- end }} 47 | 48 | 3. Login with the password from step 1 and the username: {{ .Values.adminUser }} 49 | 50 | {{- if not .Values.persistence.enabled }} 51 | ################################################################################# 52 | ###### WARNING: Persistence is disabled!!! You will lose your data when ##### 53 | ###### the Grafana pod is terminated. ##### 54 | ################################################################################# 55 | {{- end }} 56 | -------------------------------------------------------------------------------- /api-server/service/auth/auth.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023-2024 Omnissa, LLC. 3 | SPDX-License-Identifier: Apache-2.0 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package auth 17 | 18 | import ( 19 | "crypto/rand" 20 | "encoding/hex" 21 | "errors" 22 | "fmt" 23 | "strings" 24 | 25 | lru "k8s.io/utils/lru" 26 | 27 | "collie-api-server/config" 28 | "collie-api-server/service/oauth/csp" 29 | "collie-api-server/service/oauth/gitlab" 30 | "collie-api-server/service/oauth/google" 31 | ) 32 | 33 | var ( 34 | tokenCache *lru.Cache 35 | ) 36 | 37 | func init() { 38 | tokenCache = lru.New(1024) 39 | } 40 | 41 | func generateRandToken(length int) string { 42 | b := make([]byte, length) 43 | if _, err := rand.Read(b); err != nil { 44 | return "" 45 | } 46 | return hex.EncodeToString(b) 47 | } 48 | 49 | func Authenticate(token string) (AuthInfo, error) { 50 | token = strings.TrimPrefix(token, "Bearer ") 51 | token = strings.TrimPrefix(token, "Token ") 52 | 53 | parts := strings.Split(token, "/") 54 | var provider string 55 | var code string 56 | if len(parts) == 1 { 57 | provider = "api" 58 | code = parts[0] 59 | } else { 60 | provider = parts[0] 61 | code = parts[1] 62 | } 63 | 64 | if provider == "api" { 65 | return validateApiToken(code) 66 | } else if provider == "csp" { 67 | t, err := csp.Validate(code) 68 | if err != nil { 69 | return nil, err 70 | } 71 | t["orgId"] = "csp/" + t["context_name"].(string) 72 | return FromMap(t), nil 73 | } else if provider == "gitlab" { 74 | t, err := gitlab.Validate(code) 75 | if err != nil { 76 | return nil, err 77 | } 78 | t["orgId"] = "gitlab/" + fmt.Sprintf("%v", t["id"]) 79 | return FromMap(t), nil 80 | } else if provider == "google" { 81 | t, err := google.Validate(code) 82 | if err != nil { 83 | return nil, err 84 | } 85 | t["orgId"] = "google/" + fmt.Sprintf("%v", t["id"]) 86 | return FromMap(t), nil 87 | } else { 88 | return nil, errors.New("Invalid auth provider: " + provider) 89 | } 90 | } 91 | 92 | func validateApiToken(code string) (AuthInfo, error) { 93 | authInfo, ok := tokenCache.Get(code) 94 | if !ok { 95 | return nil, errors.New("OTP does not exist") 96 | } 97 | return authInfo.(AuthInfo), nil 98 | } 99 | 100 | func AddToken(token string) { 101 | tokenCache.Add(token, nil) 102 | } 103 | 104 | func GenerateApiKey(authInfo AuthInfo) string { 105 | token := authInfo.OrgId() + ":" + generateRandToken(16) 106 | tokenCache.Add(token, authInfo) 107 | return token 108 | } 109 | 110 | func GenerateEsKey(authInfo AuthInfo) string { 111 | //esUser := authInfo.OrgId() 112 | //esPwd := "gen" 113 | //return esUser + ":" + esPwd 114 | return config.Get().EsKey 115 | } 116 | -------------------------------------------------------------------------------- /k8s-agent/go.mod: -------------------------------------------------------------------------------- 1 | module collie-agent 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/cenkalti/backoff/v4 v4.2.0 7 | github.com/dustin/go-humanize v1.0.1 8 | github.com/elastic/go-elasticsearch/v8 v8.7.1 9 | github.com/go-resty/resty/v2 v2.7.0 10 | github.com/sirupsen/logrus v1.8.1 11 | github.com/spf13/viper v1.12.0 12 | k8s.io/api v0.25.4 13 | k8s.io/apimachinery v0.25.4 14 | k8s.io/client-go v0.25.4 15 | k8s.io/metrics v0.25.4 16 | sigs.k8s.io/controller-runtime v0.12.2 17 | ) 18 | 19 | require ( 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/elastic/elastic-transport-go/v8 v8.2.0 // indirect 22 | github.com/emicklei/go-restful/v3 v3.8.0 // indirect 23 | github.com/fsnotify/fsnotify v1.5.4 // indirect 24 | github.com/go-logr/logr v1.2.3 // indirect 25 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 26 | github.com/go-openapi/jsonreference v0.20.0 // indirect 27 | github.com/go-openapi/swag v0.21.1 // indirect 28 | github.com/gogo/protobuf v1.3.2 // indirect 29 | github.com/golang/protobuf v1.5.2 // indirect 30 | github.com/google/gnostic v0.6.9 // indirect 31 | github.com/google/gofuzz v1.2.0 // indirect 32 | github.com/hashicorp/hcl v1.0.0 // indirect 33 | github.com/imdario/mergo v0.3.13 // indirect 34 | github.com/josharian/intern v1.0.0 // indirect 35 | github.com/json-iterator/go v1.1.12 // indirect 36 | github.com/magiconair/properties v1.8.6 // indirect 37 | github.com/mailru/easyjson v0.7.7 // indirect 38 | github.com/mitchellh/mapstructure v1.5.0 // indirect 39 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 40 | github.com/modern-go/reflect2 v1.0.2 // indirect 41 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 42 | github.com/pelletier/go-toml v1.9.5 // indirect 43 | github.com/pelletier/go-toml/v2 v2.0.2 // indirect 44 | github.com/spf13/afero v1.8.2 // indirect 45 | github.com/spf13/cast v1.5.0 // indirect 46 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 47 | github.com/spf13/pflag v1.0.5 // indirect 48 | github.com/subosito/gotenv v1.4.0 // indirect 49 | golang.org/x/net v0.9.0 // indirect 50 | golang.org/x/oauth2 v0.0.0-20220628200809-02e64fa58f26 // indirect 51 | golang.org/x/sys v0.7.0 // indirect 52 | golang.org/x/term v0.7.0 // indirect 53 | golang.org/x/text v0.9.0 // indirect 54 | golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect 55 | google.golang.org/appengine v1.6.7 // indirect 56 | google.golang.org/protobuf v1.28.0 // indirect 57 | gopkg.in/inf.v0 v0.9.1 // indirect 58 | gopkg.in/ini.v1 v1.66.6 // indirect 59 | gopkg.in/yaml.v2 v2.4.0 // indirect 60 | gopkg.in/yaml.v3 v3.0.1 // indirect 61 | k8s.io/klog/v2 v2.70.1 // indirect 62 | k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect 63 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect 64 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 65 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 66 | sigs.k8s.io/yaml v1.3.0 // indirect 67 | ) 68 | 69 | replace ( 70 | github.com/gogo/protobuf v1.1.1 => github.com/gogo/protobuf v1.3.2 71 | github.com/gogo/protobuf v1.2.1 => github.com/gogo/protobuf v1.3.2 72 | github.com/gogo/protobuf v1.3.1 => github.com/gogo/protobuf v1.3.2 73 | ) 74 | 75 | replace github.com/chzyer/logex v1.1.10 => github.com/chzyer/logex v1.2.0 76 | -------------------------------------------------------------------------------- /api-server/model/account.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023-2024 Omnissa, LLC. 3 | SPDX-License-Identifier: Apache-2.0 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package model 17 | 18 | import ( 19 | "errors" 20 | "fmt" 21 | 22 | uuid "github.com/gofrs/uuid" 23 | ) 24 | 25 | // Account example 26 | type Account struct { 27 | ID int `json:"id" example:"1" format:"int64"` 28 | Name string `json:"name" example:"account name"` 29 | UUID uuid.UUID `json:"uuid" example:"550e8400-e29b-41d4-a716-446655440000" format:"uuid"` 30 | } 31 | 32 | // example 33 | var ( 34 | ErrNameInvalid = errors.New("name is empty") 35 | ) 36 | 37 | // AddAccount example 38 | type AddAccount struct { 39 | Name string `json:"name" example:"account name"` 40 | } 41 | 42 | // Validation example 43 | func (a AddAccount) Validation() error { 44 | switch { 45 | case len(a.Name) == 0: 46 | return ErrNameInvalid 47 | default: 48 | return nil 49 | } 50 | } 51 | 52 | // UpdateAccount example 53 | type UpdateAccount struct { 54 | Name string `json:"name" example:"account name"` 55 | } 56 | 57 | // Validation example 58 | func (a UpdateAccount) Validation() error { 59 | switch { 60 | case len(a.Name) == 0: 61 | return ErrNameInvalid 62 | default: 63 | return nil 64 | } 65 | } 66 | 67 | // AccountsAll example 68 | func AccountsAll(q string) ([]Account, error) { 69 | if q == "" { 70 | return accounts, nil 71 | } 72 | as := []Account{} 73 | for k, v := range accounts { 74 | if q == v.Name { 75 | as = append(as, accounts[k]) 76 | } 77 | } 78 | return as, nil 79 | } 80 | 81 | // AccountOne example 82 | func AccountOne(id int) (Account, error) { 83 | for _, v := range accounts { 84 | if id == v.ID { 85 | return v, nil 86 | } 87 | } 88 | return Account{}, ErrNoRow 89 | } 90 | 91 | // Insert example 92 | func (a Account) Insert() (int, error) { 93 | accountMaxID++ 94 | a.ID = accountMaxID 95 | a.Name = fmt.Sprintf("account_%d", accountMaxID) 96 | accounts = append(accounts, a) 97 | return accountMaxID, nil 98 | } 99 | 100 | // Delete example 101 | func Delete(id int) error { 102 | for k, v := range accounts { 103 | if id == v.ID { 104 | accounts = append(accounts[:k], accounts[k+1:]...) 105 | return nil 106 | } 107 | } 108 | return fmt.Errorf("account id=%d is not found", id) 109 | } 110 | 111 | // Update example 112 | func (a Account) Update() error { 113 | for k, v := range accounts { 114 | if a.ID == v.ID { 115 | accounts[k].Name = a.Name 116 | return nil 117 | } 118 | } 119 | return fmt.Errorf("account id=%d is not found", a.ID) 120 | } 121 | 122 | var accountMaxID = 3 123 | var accounts = []Account{ 124 | {ID: 1, Name: "account_1"}, 125 | {ID: 2, Name: "account_2"}, 126 | {ID: 3, Name: "account_3"}, 127 | } 128 | -------------------------------------------------------------------------------- /api-server/go.mod: -------------------------------------------------------------------------------- 1 | module collie-api-server 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/cenkalti/backoff/v4 v4.2.1 7 | github.com/elastic/go-elasticsearch/v8 v8.7.1 8 | github.com/gin-contrib/cors v1.4.0 9 | github.com/gin-gonic/gin v1.9.1 10 | github.com/gofrs/uuid v4.2.0+incompatible 11 | github.com/lestrrat-go/jwx/v2 v2.0.11 12 | github.com/sirupsen/logrus v1.9.2 13 | github.com/spf13/viper v1.15.0 14 | github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 15 | github.com/swaggo/gin-swagger v1.4.2 16 | github.com/swaggo/swag v1.16.1 17 | golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 18 | k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 19 | sigs.k8s.io/controller-runtime v0.14.6 20 | ) 21 | 22 | require ( 23 | cloud.google.com/go/compute v1.14.0 // indirect 24 | cloud.google.com/go/compute/metadata v0.2.3 // indirect 25 | github.com/KyleBanks/depth v1.2.1 // indirect 26 | github.com/bytedance/sonic v1.9.1 // indirect 27 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 28 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect 29 | github.com/elastic/elastic-transport-go/v8 v8.2.0 // indirect 30 | github.com/fsnotify/fsnotify v1.6.0 // indirect 31 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 32 | github.com/gin-contrib/sse v0.1.0 // indirect 33 | github.com/go-logr/logr v1.2.3 // indirect 34 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 35 | github.com/go-openapi/jsonreference v0.20.2 // indirect 36 | github.com/go-openapi/spec v0.20.9 // indirect 37 | github.com/go-openapi/swag v0.22.3 // indirect 38 | github.com/go-playground/locales v0.14.1 // indirect 39 | github.com/go-playground/universal-translator v0.18.1 // indirect 40 | github.com/go-playground/validator/v10 v10.14.0 // indirect 41 | github.com/goccy/go-json v0.10.2 // indirect 42 | github.com/golang/protobuf v1.5.2 // indirect 43 | github.com/hashicorp/hcl v1.0.0 // indirect 44 | github.com/josharian/intern v1.0.0 // indirect 45 | github.com/json-iterator/go v1.1.12 // indirect 46 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 47 | github.com/leodido/go-urn v1.2.4 // indirect 48 | github.com/lestrrat-go/blackmagic v1.0.1 // indirect 49 | github.com/lestrrat-go/httpcc v1.0.1 // indirect 50 | github.com/lestrrat-go/httprc v1.0.4 // indirect 51 | github.com/lestrrat-go/iter v1.0.2 // indirect 52 | github.com/lestrrat-go/option v1.0.1 // indirect 53 | github.com/magiconair/properties v1.8.7 // indirect 54 | github.com/mailru/easyjson v0.7.7 // indirect 55 | github.com/mattn/go-isatty v0.0.19 // indirect 56 | github.com/mitchellh/mapstructure v1.5.0 // indirect 57 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 58 | github.com/modern-go/reflect2 v1.0.2 // indirect 59 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 60 | github.com/segmentio/asm v1.2.0 // indirect 61 | github.com/spf13/afero v1.9.3 // indirect 62 | github.com/spf13/cast v1.5.0 // indirect 63 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 64 | github.com/spf13/pflag v1.0.5 // indirect 65 | github.com/subosito/gotenv v1.4.2 // indirect 66 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 67 | github.com/ugorji/go/codec v1.2.11 // indirect 68 | golang.org/x/arch v0.3.0 // indirect 69 | golang.org/x/crypto v0.9.0 // indirect 70 | golang.org/x/net v0.10.0 // indirect 71 | golang.org/x/sys v0.8.0 // indirect 72 | golang.org/x/text v0.9.0 // indirect 73 | golang.org/x/tools v0.9.3 // indirect 74 | google.golang.org/appengine v1.6.7 // indirect 75 | google.golang.org/protobuf v1.30.0 // indirect 76 | gopkg.in/ini.v1 v1.67.0 // indirect 77 | gopkg.in/yaml.v3 v3.0.1 // indirect 78 | k8s.io/apimachinery v0.26.1 // indirect 79 | ) 80 | -------------------------------------------------------------------------------- /api-server/controller/portal.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023-2024 Omnissa, LLC. 3 | SPDX-License-Identifier: Apache-2.0 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package controller 17 | 18 | import ( 19 | "fmt" 20 | "log" 21 | "net/http" 22 | "text/template" 23 | "time" 24 | 25 | "github.com/gin-gonic/gin" 26 | "golang.org/x/oauth2" 27 | 28 | "collie-api-server/config" 29 | "collie-api-server/httputil" 30 | middleware "collie-api-server/middleware" 31 | "collie-api-server/service/oauth/csp" 32 | "collie-api-server/service/oauth/gitlab" 33 | "collie-api-server/service/oauth/google" 34 | "collie-api-server/service/org" 35 | ) 36 | 37 | var homeUrl string = config.Require("collie.url") + "/collie/portal" 38 | 39 | func (c *Controller) PortalIndex(ctx *gin.Context) { 40 | 41 | authInfo := middleware.GetAuth(ctx) 42 | orgInfo, err := org.EnsureOnboard(authInfo.OrgId()) 43 | if err != nil { 44 | httputil.Abort(ctx, http.StatusInternalServerError, err) 45 | return 46 | } 47 | cfg := config.Get() 48 | data := gin.H{ 49 | "grafanaURL": cfg.GrafanaURL, //"https://collie.eng.omnissa.com/d/qIbLYbT4z/k8s-compliance-report" 50 | "grafanaOrgId": orgInfo.GrafanaOrgId, //"1" 51 | } 52 | ctx.HTML(http.StatusOK, "index.html", data) 53 | } 54 | 55 | func (c *Controller) PortalLogin(ctx *gin.Context) { 56 | data := map[string]interface{}{ 57 | "cspAuthUrl": csp.GetAuthUrl(), 58 | "gitlabAuthUrl": gitlab.GetAuthUrl(), 59 | "googleAuthUrl": google.GetAuthUrl(), 60 | } 61 | //ctx.HTML(http.StatusOK, "login.html", data) 62 | renderTemplate(ctx, "login.html", data) 63 | } 64 | 65 | func renderTemplate(c *gin.Context, templateName string, data gin.H) { 66 | tmpl := template.Must(template.ParseFiles("assets/" + templateName)) 67 | 68 | // Disable auto-escaping of template variables 69 | //tmpl.Option("html").EscapeHTML = false 70 | 71 | err := tmpl.ExecuteTemplate(c.Writer, templateName, data) 72 | if err != nil { 73 | // Handle the error 74 | c.AbortWithError(http.StatusInternalServerError, err) 75 | } 76 | } 77 | 78 | type fnCallback func(string, string) (*oauth2.Token, error, int) 79 | 80 | func callback(c *Controller, handleCallback fnCallback, provider string, ctx *gin.Context) { 81 | state := ctx.Query("state") 82 | code := ctx.Query("code") 83 | token, err, statusCode := handleCallback(state, code) 84 | if err != nil { 85 | log.Printf("Auth failed: %s", err.Error()) 86 | httputil.Abort(ctx, statusCode, err) 87 | } else { 88 | age := int(time.Until(token.Expiry).Seconds()) 89 | token := provider + "/" + token.AccessToken 90 | ctx.SetCookie("auth", token, age, "", "", false, true) 91 | ctx.Redirect(http.StatusTemporaryRedirect, homeUrl) 92 | } 93 | } 94 | 95 | func (c *Controller) OauthCallback(ctx *gin.Context) { 96 | provider := ctx.Param("provider") 97 | if provider == "csp" { 98 | callback(c, csp.HandleCallback, provider, ctx) 99 | } else if provider == "gitlab" { 100 | callback(c, gitlab.HandleCallback, provider, ctx) 101 | } else if provider == "google" { 102 | callback(c, google.HandleCallback, provider, ctx) 103 | } else { 104 | httputil.Abort(ctx, http.StatusBadRequest, fmt.Errorf("Unknown provider: %s", provider)) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /dashboard/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | wheels/ 22 | share/python-wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .nox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | *.py,cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | cover/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | .pybuilder/ 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | # For a library or package, you might want to ignore these files since the code is 86 | # intended to run in multiple environments; otherwise, check them in: 87 | # .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # poetry 97 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 98 | # This is especially recommended for binary packages to ensure reproducibility, and is more 99 | # commonly ignored for libraries. 100 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 101 | #poetry.lock 102 | 103 | # pdm 104 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 105 | #pdm.lock 106 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 107 | # in version control. 108 | # https://pdm.fming.dev/#use-with-ide 109 | .pdm.toml 110 | 111 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 112 | __pypackages__/ 113 | 114 | # Celery stuff 115 | celerybeat-schedule 116 | celerybeat.pid 117 | 118 | # SageMath parsed files 119 | *.sage.py 120 | 121 | # Environments 122 | .venv 123 | env/ 124 | venv/ 125 | ENV/ 126 | env.bak/ 127 | venv.bak/ 128 | 129 | # Spyder project settings 130 | .spyderproject 131 | .spyproject 132 | 133 | # Rope project settings 134 | .ropeproject 135 | 136 | # mkdocs documentation 137 | /site 138 | 139 | # mypy 140 | .mypy_cache/ 141 | .dmypy.json 142 | dmypy.json 143 | 144 | # Pyre type checker 145 | .pyre/ 146 | 147 | # pytype static type analyzer 148 | .pytype/ 149 | 150 | # Cython debug symbols 151 | cython_debug/ 152 | 153 | # PyCharm 154 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 155 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 156 | # and can be added to the global gitignore or merged into this file. For a more nuclear 157 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 158 | #.idea/ 159 | 160 | poller/data/ 161 | tmp/ 162 | data/ 163 | 164 | -------------------------------------------------------------------------------- /api-server/commonms/healthz.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023-2024 Omnissa, LLC. 3 | SPDX-License-Identifier: Apache-2.0 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package commonms 17 | 18 | import ( 19 | "fmt" 20 | "github.com/sirupsen/logrus" 21 | "net/http" 22 | "time" 23 | 24 | "sigs.k8s.io/controller-runtime/pkg/healthz" 25 | 26 | "collie-api-server/config" 27 | ) 28 | 29 | func newHealthzProvider(cfg config.Config, log logrus.FieldLogger) *HealthzProvider { 30 | return &HealthzProvider{ 31 | cfg: cfg, 32 | log: log, 33 | initHardTimeout: cfg.Controller.PrepTimeout + cfg.Controller.InitialSleepDuration + cfg.Controller.InitializationTimeoutExtension, 34 | } 35 | } 36 | 37 | type HealthzProvider struct { 38 | cfg config.Config 39 | log logrus.FieldLogger 40 | initHardTimeout time.Duration 41 | 42 | initializeStartedAt *time.Time 43 | lastHealthyActionAt *time.Time 44 | } 45 | 46 | func (h *HealthzProvider) Check(_ *http.Request) (err error) { 47 | defer func() { 48 | if err != nil { 49 | h.log.Warnf("Health check failed due to: %v", err) 50 | } 51 | }() 52 | 53 | if h.lastHealthyActionAt != nil { 54 | if time.Since(*h.lastHealthyActionAt) > h.cfg.Controller.HealthySnapshotIntervalLimit { 55 | return fmt.Errorf("time since initialization or last snapshot sent is over the considered healthy limit of %s", h.cfg.Controller.HealthySnapshotIntervalLimit) 56 | } 57 | return nil 58 | } 59 | 60 | if h.initializeStartedAt != nil { 61 | if time.Since(*h.initializeStartedAt) > h.initHardTimeout { 62 | return fmt.Errorf("controller initialization is taking longer than the hard timeout of %s", h.initHardTimeout) 63 | } 64 | return nil 65 | } 66 | 67 | return nil 68 | } 69 | 70 | func (h *HealthzProvider) Initializing() { 71 | if h.initializeStartedAt == nil { 72 | h.initializeStartedAt = nowPtr() 73 | h.lastHealthyActionAt = nil 74 | } 75 | } 76 | 77 | func (h *HealthzProvider) Initialized() { 78 | h.healthyAction() 79 | } 80 | 81 | func (h *HealthzProvider) SnapshotSent() { 82 | h.healthyAction() 83 | } 84 | 85 | func (h *HealthzProvider) healthyAction() { 86 | h.initializeStartedAt = nil 87 | h.lastHealthyActionAt = nowPtr() 88 | } 89 | 90 | func nowPtr() *time.Time { 91 | now := time.Now() 92 | return &now 93 | } 94 | 95 | func runHealthzEndpoints(cfg config.Config, log *logrus.Entry, controllerCheck healthz.Checker, exitCh chan error) func() { 96 | log.Infof("starting healthz on port: %d", cfg.HealthzPort) 97 | healthzSrv := &http.Server{Addr: portToServerAddr(cfg.HealthzPort), Handler: &healthz.Handler{Checks: map[string]healthz.Checker{ 98 | "server": healthz.Ping, 99 | "controller": controllerCheck, 100 | }}} 101 | closeFunc := func() { 102 | if err := healthzSrv.Close(); err != nil { 103 | log.Errorf("closing healthz server: %v", err) 104 | } 105 | } 106 | 107 | go func() { 108 | exitCh <- fmt.Errorf("healthz server: %w", healthzSrv.ListenAndServe()) 109 | }() 110 | return closeFunc 111 | } 112 | 113 | func portToServerAddr(port int) string { 114 | return fmt.Sprintf(":%d", port) 115 | } 116 | 117 | func StartHealthz(cfg config.Config, log *logrus.Entry, exitCh chan error) func() { 118 | ctrlHealthz := newHealthzProvider(cfg, log) 119 | closeHealthz := runHealthzEndpoints(cfg, log, ctrlHealthz.Check, exitCh) 120 | return closeHealthz 121 | } 122 | -------------------------------------------------------------------------------- /k8s-agent/internal/commonms/healthz.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023-2024 Omnissa, LLC. 3 | SPDX-License-Identifier: Apache-2.0 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package commonms 17 | 18 | import ( 19 | "fmt" 20 | "net/http" 21 | "time" 22 | 23 | "github.com/sirupsen/logrus" 24 | 25 | "sigs.k8s.io/controller-runtime/pkg/healthz" 26 | 27 | "collie-agent/internal/config" 28 | ) 29 | 30 | func newHealthzProvider(cfg config.Config, log logrus.FieldLogger) *HealthzProvider { 31 | return &HealthzProvider{ 32 | cfg: cfg, 33 | log: log, 34 | initHardTimeout: cfg.Controller.PrepTimeout + cfg.Controller.InitialSleepDuration + cfg.Controller.InitializationTimeoutExtension, 35 | } 36 | } 37 | 38 | type HealthzProvider struct { 39 | cfg config.Config 40 | log logrus.FieldLogger 41 | initHardTimeout time.Duration 42 | 43 | initializeStartedAt *time.Time 44 | lastHealthyActionAt *time.Time 45 | } 46 | 47 | func now() *time.Time { 48 | now := time.Now() 49 | return &now 50 | } 51 | 52 | func (h *HealthzProvider) healthyAction() { 53 | h.initializeStartedAt = nil 54 | h.lastHealthyActionAt = now() 55 | } 56 | 57 | func runHealthzEndpoints(cfg config.Config, log *logrus.Entry, controllerCheck healthz.Checker, exitCh chan error) func() { 58 | log.Infof("starting healthz on port: %d", cfg.HealthzPort) 59 | healthzSrv := &http.Server{Addr: portToServerAddr(cfg.HealthzPort), Handler: &healthz.Handler{Checks: map[string]healthz.Checker{ 60 | "server": healthz.Ping, 61 | "controller": controllerCheck, 62 | }}} 63 | closeFunc := func() { 64 | if err := healthzSrv.Close(); err != nil { 65 | log.Errorf("closing healthz server: %v", err) 66 | } 67 | } 68 | 69 | go func() { 70 | exitCh <- fmt.Errorf("healthz server: %w", healthzSrv.ListenAndServe()) 71 | }() 72 | return closeFunc 73 | } 74 | 75 | func portToServerAddr(port int) string { 76 | return fmt.Sprintf(":%d", port) 77 | } 78 | 79 | func (h *HealthzProvider) Check(_ *http.Request) (err error) { 80 | defer func() { 81 | if err != nil { 82 | h.log.Warnf("Health check failed due to: %v", err) 83 | } 84 | }() 85 | 86 | if h.lastHealthyActionAt != nil { 87 | if time.Since(*h.lastHealthyActionAt) > h.cfg.Controller.HealthySnapshotIntervalLimit { 88 | return fmt.Errorf("time since initialization or last snapshot sent is over the considered healthy limit of %s", h.cfg.Controller.HealthySnapshotIntervalLimit) 89 | } 90 | return nil 91 | } 92 | 93 | if h.initializeStartedAt != nil { 94 | if time.Since(*h.initializeStartedAt) > h.initHardTimeout { 95 | return fmt.Errorf("controller initialization is taking longer than the hard timeout of %s", h.initHardTimeout) 96 | } 97 | return nil 98 | } 99 | 100 | return nil 101 | } 102 | 103 | func (h *HealthzProvider) Initializing() { 104 | if h.initializeStartedAt == nil { 105 | h.initializeStartedAt = now() 106 | h.lastHealthyActionAt = nil 107 | } 108 | } 109 | 110 | func (h *HealthzProvider) Initialized() { 111 | h.healthyAction() 112 | } 113 | 114 | func (h *HealthzProvider) SnapshotSent() { 115 | h.healthyAction() 116 | } 117 | 118 | func StartHealthz(cfg config.Config, log *logrus.Entry, exitCh chan error) func() { 119 | ctrlHealthz := newHealthzProvider(cfg, log) 120 | closeHealthz := runHealthzEndpoints(cfg, log, ctrlHealthz.Check, exitCh) 121 | return closeHealthz 122 | } 123 | --------------------------------------------------------------------------------