mirror of
https://github.com/FairwindsOps/polaris.git
synced 2026-08-29 22:17:16 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
962494e47a | ||
|
|
c816e60441 | ||
|
|
d46fd6215a | ||
|
|
7bcd01e6be | ||
|
|
20a6028145 | ||
|
|
f784c48217 | ||
|
|
9b755b8b93 | ||
|
|
29261ed8bf | ||
|
|
1b5557d6ed | ||
|
|
3fd3c04429 | ||
|
|
5b9257b4ce | ||
|
|
d8d0783f29 | ||
|
|
4c7429efbc | ||
|
|
73727bd9d8 |
@@ -3,3 +3,8 @@ DOCKERFILE='Dockerfile'
|
||||
EXTERNAL_REGISTRY_BASE_DOMAIN=quay.io
|
||||
REPOSITORY_NAME=reactiveops/polaris
|
||||
DOCKERTAG=${EXTERNAL_REGISTRY_BASE_DOMAIN}/${REPOSITORY_NAME}
|
||||
if [[ -n $CI_TAG ]]; then
|
||||
ADDITIONAL_DOCKER_TAG_VERSIONS=()
|
||||
ADDITIONAL_DOCKER_TAG_VERSIONS+=(`echo $CI_TAG | sed -e 's/\(\w\+\)\..*$/\1/'`)
|
||||
ADDITIONAL_DOCKER_TAG_VERSIONS+=(`echo $CI_TAG | sed -e 's/\(\w\+\.\w\+\)\..*$/\1/'`)
|
||||
fi
|
||||
|
||||
+76
-25
@@ -1,4 +1,9 @@
|
||||
version: 2
|
||||
version: 2.1
|
||||
|
||||
executors:
|
||||
vm:
|
||||
machine:
|
||||
enabled: true
|
||||
|
||||
references:
|
||||
set_environment_variables: &set_environment_variables
|
||||
@@ -10,21 +15,33 @@ references:
|
||||
echo 'export CI_BUILD_NUM=$CIRCLE_BUILD_NUM' >> ${BASH_ENV}
|
||||
echo 'export CI_TAG=$CIRCLE_TAG' >> ${BASH_ENV}
|
||||
echo 'export PUSH_ALL_VERSION_TAGS=true' >> ${BASH_ENV}
|
||||
docker_build_and_push: &docker_build_and_push
|
||||
install_k8s: &install_k8s
|
||||
run:
|
||||
name: Docker login, build, and push
|
||||
name: Install K8s
|
||||
command: |
|
||||
docker-pull -f .circleci/build.config
|
||||
docker-build -f .circleci/build.config
|
||||
if [[ -n $CIRCLE_TAG ]]; then
|
||||
export ADDITIONAL_DOCKER_TAG_VERSIONS=(`echo $CIRCLE_TAG | sed -e 's/\(\w\+\)\..*$/\1/'`)
|
||||
fi
|
||||
if [[ -z $CIRCLE_PR_NUMBER ]]; then
|
||||
docker login quay.io -u="reactiveops+circleci" -p="${quay_token}"
|
||||
docker-push -f .circleci/build.config
|
||||
else
|
||||
echo "Skipping docker push for forked PR"
|
||||
fi
|
||||
echo "Installing git and jq"
|
||||
sudo apt-get install -yqq jq git
|
||||
|
||||
echo "Installing KIND"
|
||||
curl -sLO https://github.com/kubernetes-sigs/kind/releases/download/0.2.1/kind-linux-amd64
|
||||
chmod 0755 kind-linux-amd64
|
||||
sudo mv kind-linux-amd64 /usr/local/bin/kind
|
||||
kind version
|
||||
|
||||
echo "Installing Kubectl"
|
||||
curl -sLO https://storage.googleapis.com/kubernetes-release/release/v1.12.7/bin/linux/amd64/kubectl
|
||||
chmod 0755 kubectl
|
||||
sudo mv kubectl /usr/local/bin/
|
||||
kubectl version --client
|
||||
|
||||
|
||||
echo "Creating Kubernetes Cluster with Kind"
|
||||
kind create cluster --wait=90s
|
||||
docker ps -a
|
||||
|
||||
echo "Setting up kubecfg"
|
||||
cp $(kind get kubeconfig-path --name=kind) ~/.kube/config
|
||||
kubectl version
|
||||
|
||||
# Test scripts
|
||||
update_coverage: &update_coverage
|
||||
@@ -37,7 +54,7 @@ references:
|
||||
else
|
||||
echo "Skipping coverage for forked PR"
|
||||
fi
|
||||
test_dashboard: &test_dashboard
|
||||
test_binary_dashboard: &test_binary_dashboard
|
||||
run:
|
||||
name: Test Dashboard
|
||||
command: |
|
||||
@@ -49,6 +66,21 @@ references:
|
||||
curl -f http://localhost:3000/static/css/main.css > /dev/null
|
||||
curl -f http://localhost:3000/results.json > /dev/null
|
||||
curl -f http://localhost:3000/details/security > /dev/null
|
||||
test_kube_dashboard: &test_kube_dashboard
|
||||
run:
|
||||
name: Test Dashboard
|
||||
command: |
|
||||
kubectl apply -f ./deploy/dashboard.yaml
|
||||
sleep 10
|
||||
kubectl get pods --namespace polaris
|
||||
kubectl port-forward --namespace polaris svc/polaris-dashboard 3000:80 &
|
||||
sleep 5
|
||||
curl -f http://localhost:3000 > /dev/null
|
||||
curl -f http://localhost:3000/health > /dev/null
|
||||
curl -f http://localhost:3000/favicon.ico > /dev/null
|
||||
curl -f http://localhost:3000/static/css/main.css > /dev/null
|
||||
curl -f http://localhost:3000/results.json > /dev/null
|
||||
curl -f http://localhost:3000/details/security > /dev/null
|
||||
|
||||
# Release scripts
|
||||
install_goreleaser: &install_goreleaser
|
||||
@@ -59,20 +91,26 @@ references:
|
||||
echo "8dbad6683d6fc9367e637e6eed8e01a0d63c9660 goreleaser.deb" | sha1sum -c
|
||||
sudo dpkg -i goreleaser.deb
|
||||
rm goreleaser.deb
|
||||
docker_build_and_push: &docker_build_and_push
|
||||
run:
|
||||
name: Docker login, build, and push
|
||||
command: |
|
||||
docker-pull -f .circleci/build.config
|
||||
docker-build -f .circleci/build.config
|
||||
if [[ -z $CIRCLE_PR_NUMBER ]]; then
|
||||
docker login quay.io -u="reactiveops+circleci" -p="${quay_token}"
|
||||
docker-push -f .circleci/build.config
|
||||
else
|
||||
echo "Skipping docker push for forked PR"
|
||||
fi
|
||||
release_deploy_configs: &release_deploy_configs
|
||||
run:
|
||||
name: Release deploy configs
|
||||
command: |
|
||||
git clone --branch polaris-latest https://github.com/reactiveops/charts
|
||||
mkdir deploy
|
||||
helm template ./charts/stable/polaris --name polaris --namespace polaris --set templateOnly=true > deploy/dashboard.yaml
|
||||
helm template ./charts/stable/polaris --name polaris --namespace polaris --set templateOnly=true --set webhook.enable=true --set dashboard.enable=false > deploy/webhook.yaml
|
||||
upload_url=$(curl --silent https://api.github.com/repos/reactiveops/polaris/releases/latest | grep upload_url)
|
||||
upload_url=$(curl --silent https://api.github.com/repos/FairwindsOps/polaris/releases/latest | grep upload_url)
|
||||
upload_url=$(echo $upload_url | sed -e 's/.*\(https.*\){.*$/\1/')
|
||||
curl -X POST "$upload_url?name=dashboard.yaml" --data-binary "@./deploy/dashboard.yaml" -H "Authorization: Bearer $GITHUB_TOKEN" -H "Content-Type: application/x-yaml"
|
||||
curl -X POST "$upload_url?name=webhook.yaml" --data-binary "@./deploy/webhook.yaml" -H "Authorization: Bearer $GITHUB_TOKEN" -H "Content-Type: application/x-yaml"
|
||||
rm -rf ./charts
|
||||
rm -rf ./deploy
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -84,18 +122,26 @@ jobs:
|
||||
- *set_environment_variables
|
||||
- *docker_build_and_push
|
||||
|
||||
test_k8s:
|
||||
working_directory: ~/polaris
|
||||
resource_class: medium
|
||||
executor: vm
|
||||
steps:
|
||||
- checkout
|
||||
- *install_k8s
|
||||
- *test_kube_dashboard
|
||||
|
||||
test:
|
||||
working_directory: /go/src/github.com/reactiveops/polaris/
|
||||
docker:
|
||||
- image: circleci/golang:1.12
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
- run: go get -u golang.org/x/lint/golint
|
||||
- run: go list ./... | grep -v vendor | xargs golint -set_exit_status
|
||||
- run: go list ./... | grep -v vendor | xargs go vet
|
||||
- *update_coverage
|
||||
- *test_dashboard
|
||||
- *test_binary_dashboard
|
||||
|
||||
release_binary:
|
||||
working_directory: /go/src/github.com/reactiveops/polaris/
|
||||
@@ -109,6 +155,7 @@ jobs:
|
||||
- run: go get -u github.com/gobuffalo/packr/v2/packr2
|
||||
- run: packr2
|
||||
- run: goreleaser
|
||||
- *release_deploy_configs
|
||||
|
||||
release_images:
|
||||
working_directory: /go/src/github.com/reactiveops/polaris/
|
||||
@@ -119,13 +166,17 @@ jobs:
|
||||
- setup_remote_docker
|
||||
- *set_environment_variables
|
||||
- *docker_build_and_push
|
||||
- *release_deploy_configs
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build:
|
||||
jobs:
|
||||
- test
|
||||
- test_k8s:
|
||||
# Ignore update-version branch, which changes deploy/ image references before the images are built
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*\/update-version/
|
||||
- build:
|
||||
requires:
|
||||
- test
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
# x.x.x (next release)
|
||||
|
||||
# 0.3.0
|
||||
* Added `--set-exit-code-on-error` and `--set-exit-code-below-score` flags to better support CI/CD
|
||||
|
||||
# 0.2.1
|
||||
* [Fix](https://github.com/reactiveops/polaris/issues/146): Fixed logic on RunAsNonRoot check to incorporate settings in podSpec
|
||||
|
||||
# 0.2.0
|
||||
* Added `--output-format` flag for better CI/CD support
|
||||
* Added `--display-name` flag
|
||||
|
||||
+39
-20
@@ -58,35 +58,54 @@ Each new pull request should:
|
||||
|
||||
## Creating a new release
|
||||
|
||||
### Minor/patch releases
|
||||
Minor and patch releases only need to change this repo. The Helm chart and deploy scripts
|
||||
### Patch releases
|
||||
Patch releases only need to change this repo. The Helm chart and deploy scripts
|
||||
will automatically pull in the latest changes.
|
||||
|
||||
To deploy a minor or patch release, follow steps 2 and 3 from "Major releases" below.
|
||||
If the release involves changes to anything in the `deploy/` folder (e.g. new RBAC permissions),
|
||||
it needs to be a minor or major release in order to prevent breaking the Helm chart.
|
||||
|
||||
### Major releases
|
||||
Major releases need to change both this repository and the
|
||||
[Helm chart repo](https://github.com/reactiveops/charts/).
|
||||
|
||||
The steps are:
|
||||
1. Create a PR in the [charts repo](https://github.com/reactiveops/charts/)
|
||||
1. Use a branch named `polaris-latest`
|
||||
2. Bump the version number in:
|
||||
1. stable/polaris/README.md
|
||||
2. stable/polaris/Chart.yaml
|
||||
3. stable/polaris/values.yaml
|
||||
3. **Don't merge yet!**
|
||||
2. Create a PR for this repo
|
||||
1. Create a PR for this repo
|
||||
1. Bump the version number in:
|
||||
1. main.go
|
||||
2. README.md
|
||||
2. Update CHANGELOG.md
|
||||
3. Merge your PR
|
||||
2. Tag the latest branch for this repo
|
||||
1. Pull the latest commit for the `master` branch (which you just merged in your PR)
|
||||
2. Run `git tag $VERSION && git push --tags`
|
||||
3. Make sure CircleCI runs successfully for the new tag - this will push images to quay.io and create a release in GitHub
|
||||
1. If CircleCI fails, check with Codeowners ASAP
|
||||
|
||||
### Minor/Major releases
|
||||
Minor and major releases need to change both this repository and the
|
||||
[Helm chart repo](https://github.com/reactiveops/charts/).
|
||||
|
||||
The steps are:
|
||||
1. Modify the [Helm chart](https://github.com/reactiveops/charts/stable/polaris)
|
||||
1. Clone the helm charts repo
|
||||
1. `git clone https://github.com/reactiveops/charts`
|
||||
2. `git checkout -b yourname/update-polaris`
|
||||
1. Bump the version number in:
|
||||
1. stable/polaris/README.md
|
||||
2. stable/polaris/Chart.yaml
|
||||
3. stable/polaris/values.yaml
|
||||
2. Make any necessary changes to the chart to support the new version of Polaris (e.g. new RBAC permissions)
|
||||
3. **Don't merge yet!**
|
||||
2. Create a PR for this repo
|
||||
1. Create a new branch named `yourname/update-version`
|
||||
2. Bump the version number in:
|
||||
1. main.go
|
||||
2. README.md
|
||||
3. Regenerate the deployment files. Assuming you've cloned the charts repo to `./charts`:
|
||||
1. `helm template ./charts/stable/polaris/ --name polaris --namespace polaris --set templateOnly=true > deploy/dashboard.yaml`
|
||||
2. `helm template ./charts/stable/polaris/ --name polaris --namespace polaris --set templateOnly=true --set webhook.enable=true --set dashboard.enable=false > deploy/webhook.yaml`
|
||||
4. Update CHANGELOG.md
|
||||
5. Merge your PR
|
||||
3. Tag the latest branch for this repo
|
||||
1. Pull the latest for the `master` branch
|
||||
2. Run `git tag $VERSION && git push --tags`
|
||||
3. Wait for CircleCI to finish the build for the tag, which will:
|
||||
1. Create the proper image tag in quay.io
|
||||
2. Add an entry to the releases page on GitHub
|
||||
4. Merge the PR for the charts repo you created in step 1.
|
||||
3. Make sure CircleCI runs successfully for the new tag - this will push images to quay.io and create a release in GitHub
|
||||
1. If CircleCI fails, check with Codeowners ASAP
|
||||
4. Create and merge a PR for your changes to the Helm chart
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
[![Version][version-image]][version-link] [![CircleCI][circleci-image]][circleci-link] [![Go Report Card][goreport-image]][goreport-link]
|
||||
</div>
|
||||
|
||||
[version-image]: https://img.shields.io/static/v1.svg?label=Version&message=0.2.0&color=239922
|
||||
[version-image]: https://img.shields.io/static/v1.svg?label=Version&message=0.3.0&color=239922
|
||||
[version-link]: https://github.com/reactiveops/polaris
|
||||
|
||||
[goreport-image]: https://goreportcard.com/badge/github.com/reactiveops/polaris
|
||||
@@ -14,10 +14,13 @@
|
||||
[circleci-image]: https://circleci.com/gh/reactiveops/polaris.svg?style=svg
|
||||
[circleci-link]: https://circleci.com/gh/reactiveops/polaris.svg
|
||||
|
||||
Polaris helps keep your cluster healthy. It runs a variety of checks to ensure that Kubernetes deployments are configured using best practices that will avoid potential problems in the future. The project includes two primary components:
|
||||
Polaris helps keep your cluster healthy. It runs a variety of checks to ensure that
|
||||
Kubernetes deployments are configured using best practices, helping you avoid
|
||||
problems in the future. Polaris can be run in a few different modes:
|
||||
|
||||
- A dashboard that provides an overview of how well current deployments are configured within a cluster.
|
||||
- An experimental validating webhook that can prevent any future deployments that do not live up to a configured standard.
|
||||
- A command-line audit that can be incorporated into your CI/CD pipeline
|
||||
|
||||
**Want to learn more?** ReactiveOps holds [office hours on Zoom](https://zoom.us/j/242508205) the first Friday of every month, at 12pm Eastern. You can also reach out via email at `opensource@reactiveops.com`
|
||||
|
||||
@@ -115,14 +118,11 @@ polaris --audit --audit-path ./deploy/
|
||||
|
||||
##### Running with CI/CD
|
||||
You can integrate Polaris into CI/CD for repositories containing infrastructure-as-code.
|
||||
For example, to fail whenever the Polaris score drops below 90%:
|
||||
For example, to fail if polaris detects *any* error-level issues, or if the score drops below 90%:
|
||||
```bash
|
||||
score=`polaris --audit --audit-path ./deploy/ --output-format score`
|
||||
if [[ $score -lt 90 ]]; then
|
||||
exit 1
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
polaris --audit --audit-path ./deploy/ \
|
||||
--set-exit-code-on-error \
|
||||
--set-exit-code-below-score 90
|
||||
```
|
||||
|
||||
## Configuration
|
||||
@@ -141,14 +141,53 @@ Polaris validation checks fall into several different categories:
|
||||
|
||||
## CLI Options
|
||||
|
||||
* `config`: Specify a location for the Polaris config
|
||||
* `dashboard`: Runs the webserver for Polaris dashboard.
|
||||
* `dashboard-port`: Port for the dashboard webserver (default `8080`)
|
||||
* `dashboard-base-path`: Path on which the dashboard is being served (default `/`)
|
||||
* `webhook`: Runs the webhook webserver.
|
||||
* `webhook-port`: Port for the webhook webserver (default `9876`)
|
||||
* `disable-webhook-config-installer`: disable the installer in the webhook server, so it won't install webhook configuration resources during bootstrapping
|
||||
* `kubeconfig`: Paths to a kubeconfig. Only required if out-of-cluster.
|
||||
```
|
||||
# high-level flags
|
||||
-version
|
||||
Prints the version of Polaris
|
||||
-config string
|
||||
Location of Polaris configuration file
|
||||
-kubeconfig string
|
||||
Path to a kubeconfig. Only required if out-of-cluster.
|
||||
-log-level string
|
||||
Logrus log level (default "info")
|
||||
-master string
|
||||
The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.
|
||||
|
||||
# dashboard flags
|
||||
-dashboard
|
||||
Runs the webserver for Polaris dashboard.
|
||||
-dashboard-base-path string
|
||||
Path on which the dashboard is served (default "/")
|
||||
-dashboard-port int
|
||||
Port for the dashboard webserver (default 8080)
|
||||
-display-name string
|
||||
An optional identifier for the audit
|
||||
|
||||
# audit flags
|
||||
-audit
|
||||
Runs a one-time audit.
|
||||
-audit-path string
|
||||
If specified, audits one or more YAML files instead of a cluster
|
||||
-output-file string
|
||||
Destination file for audit results
|
||||
-output-format string
|
||||
Output format for results - json, yaml, or score (default "json")
|
||||
-output-url string
|
||||
Destination URL to send audit results
|
||||
-set-exit-code-below-score int
|
||||
When running with --audit, set an exit code of 4 when the score is below this threshold (1-100)
|
||||
-set-exit-code-on-error
|
||||
When running with --audit, set an exit code of 3 when the audit contains error-level issues.
|
||||
|
||||
# webhook flags
|
||||
-webhook
|
||||
Runs the webhook webserver.
|
||||
-webhook-port int
|
||||
Port for the webhook webserver (default 9876)
|
||||
-disable-webhook-config-installer
|
||||
disable the installer in the webhook server, so it won't install webhook configuration resources during bootstrapping
|
||||
```
|
||||
|
||||
## Contributing
|
||||
PRs welcome! Check out the [Contributing Guidelines](CONTRIBUTING.md),
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
---
|
||||
# Source: polaris/templates/0-namespace.yaml
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: polaris
|
||||
---
|
||||
# Source: polaris/templates/configmap.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: polaris
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
data:
|
||||
config.yaml: |
|
||||
resources:
|
||||
cpuRequestsMissing: warning
|
||||
cpuLimitsMissing: warning
|
||||
memoryRequestsMissing: warning
|
||||
memoryLimitsMissing: warning
|
||||
images:
|
||||
tagNotSpecified: error
|
||||
healthChecks:
|
||||
readinessProbeMissing: warning
|
||||
livenessProbeMissing: warning
|
||||
networking:
|
||||
hostNetworkSet: warning
|
||||
hostPortSet: warning
|
||||
security:
|
||||
hostIPCSet: error
|
||||
hostPIDSet: error
|
||||
notReadOnlyRootFileSystem: warning
|
||||
privilegeEscalationAllowed: error
|
||||
runAsRootAllowed: warning
|
||||
runAsPrivileged: error
|
||||
capabilities:
|
||||
error:
|
||||
ifAnyAdded:
|
||||
- SYS_ADMIN
|
||||
- NET_ADMIN
|
||||
- ALL
|
||||
warning:
|
||||
ifAnyAddedBeyond:
|
||||
- CHOWN
|
||||
- DAC_OVERRIDE
|
||||
- FSETID
|
||||
- FOWNER
|
||||
- MKNOD
|
||||
- NET_RAW
|
||||
- SETGID
|
||||
- SETUID
|
||||
- SETFCAP
|
||||
- SETPCAP
|
||||
- NET_BIND_SERVICE
|
||||
- SYS_CHROOT
|
||||
- KILL
|
||||
- AUDIT_WRITE
|
||||
|
||||
---
|
||||
# Source: polaris/templates/dashboard.rbac.yaml
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: polaris-dashboard
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: polaris-dashboard
|
||||
labels:
|
||||
app: polaris
|
||||
rules:
|
||||
- apiGroups:
|
||||
- 'apps'
|
||||
- 'extensions'
|
||||
resources:
|
||||
- 'deployments'
|
||||
- 'statefulsets'
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'list'
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- 'nodes'
|
||||
- 'namespaces'
|
||||
- 'pods'
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'list'
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: polaris-dashboard
|
||||
labels:
|
||||
app: polaris
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: polaris-dashboard
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: polaris-dashboard
|
||||
namespace: polaris
|
||||
---
|
||||
# Source: polaris/templates/dashboard.service.yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: polaris-dashboard
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
spec:
|
||||
ports:
|
||||
- name: dashboard
|
||||
port: 80
|
||||
protocol: TCP
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: polaris
|
||||
component: dashboard
|
||||
type: ClusterIP
|
||||
---
|
||||
# Source: polaris/templates/dashboard.deployment.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: '6ec4a4dc87403cae67c01438398a5f1d4ef836ffeaf26a33b685c066b940495a'
|
||||
name: polaris-dashboard
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
component: dashboard
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: polaris
|
||||
component: dashboard
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: polaris
|
||||
component: dashboard
|
||||
spec:
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: polaris
|
||||
containers:
|
||||
- command:
|
||||
- polaris
|
||||
- --dashboard
|
||||
- --config
|
||||
- /opt/app/config.yaml
|
||||
image: 'quay.io/reactiveops/polaris:0.3'
|
||||
imagePullPolicy: 'Always'
|
||||
name: dashboard
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 20
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 20
|
||||
resources:
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /opt/app/config.yaml
|
||||
subPath: config.yaml
|
||||
readOnly: true
|
||||
serviceAccountName: polaris-dashboard
|
||||
---
|
||||
# Source: polaris/templates/audit.job.yaml
|
||||
|
||||
---
|
||||
# Source: polaris/templates/audit.rbac.yaml
|
||||
|
||||
---
|
||||
# Source: polaris/templates/ingress.yaml
|
||||
|
||||
---
|
||||
# Source: polaris/templates/webhook.deployment.yaml
|
||||
|
||||
---
|
||||
# Source: polaris/templates/webhook.rbac.yaml
|
||||
|
||||
---
|
||||
# Source: polaris/templates/webhook.secret.yaml
|
||||
|
||||
---
|
||||
# Source: polaris/templates/webhook.service.yaml
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
---
|
||||
# Source: polaris/templates/0-namespace.yaml
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: polaris
|
||||
---
|
||||
# Source: polaris/templates/webhook.secret.yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: polaris-webhook
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
type: Opaque
|
||||
stringData:
|
||||
cert.pem: ''
|
||||
---
|
||||
# Source: polaris/templates/configmap.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: polaris
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
data:
|
||||
config.yaml: |
|
||||
resources:
|
||||
cpuRequestsMissing: warning
|
||||
cpuLimitsMissing: warning
|
||||
memoryRequestsMissing: warning
|
||||
memoryLimitsMissing: warning
|
||||
images:
|
||||
tagNotSpecified: error
|
||||
healthChecks:
|
||||
readinessProbeMissing: warning
|
||||
livenessProbeMissing: warning
|
||||
networking:
|
||||
hostNetworkSet: warning
|
||||
hostPortSet: warning
|
||||
security:
|
||||
hostIPCSet: error
|
||||
hostPIDSet: error
|
||||
notReadOnlyRootFileSystem: warning
|
||||
privilegeEscalationAllowed: error
|
||||
runAsRootAllowed: warning
|
||||
runAsPrivileged: error
|
||||
capabilities:
|
||||
error:
|
||||
ifAnyAdded:
|
||||
- SYS_ADMIN
|
||||
- NET_ADMIN
|
||||
- ALL
|
||||
warning:
|
||||
ifAnyAddedBeyond:
|
||||
- CHOWN
|
||||
- DAC_OVERRIDE
|
||||
- FSETID
|
||||
- FOWNER
|
||||
- MKNOD
|
||||
- NET_RAW
|
||||
- SETGID
|
||||
- SETUID
|
||||
- SETFCAP
|
||||
- SETPCAP
|
||||
- NET_BIND_SERVICE
|
||||
- SYS_CHROOT
|
||||
- KILL
|
||||
- AUDIT_WRITE
|
||||
|
||||
---
|
||||
# Source: polaris/templates/webhook.rbac.yaml
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: polaris-webhook
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: polaris-webhook
|
||||
labels:
|
||||
app: polaris
|
||||
rules:
|
||||
- apiGroups:
|
||||
- 'apps'
|
||||
- 'extensions'
|
||||
resources:
|
||||
- 'deployments'
|
||||
- 'statefulsets'
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'list'
|
||||
# required by controller-runtime code doing a cluster wide lookup
|
||||
# when it seems namespace would suffice
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- 'secrets'
|
||||
- 'services'
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'list'
|
||||
- 'watch'
|
||||
- apiGroups:
|
||||
- 'admissionregistration.k8s.io'
|
||||
resources:
|
||||
- 'validatingwebhookconfigurations'
|
||||
verbs:
|
||||
- '*'
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: polaris-webhook
|
||||
labels:
|
||||
app: polaris
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: polaris-webhook
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: polaris-webhook
|
||||
namespace: polaris
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: polaris-webhook
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
rules:
|
||||
# required for current controller-runtime bootstrap method
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- 'secrets'
|
||||
- 'services'
|
||||
verbs:
|
||||
- '*'
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: polaris-webhook
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: polaris-webhook
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: polaris-webhook
|
||||
namespace: polaris
|
||||
---
|
||||
# Source: polaris/templates/webhook.service.yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: polaris-webhook
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
spec:
|
||||
ports:
|
||||
- name: webhook
|
||||
port: 443
|
||||
protocol: TCP
|
||||
targetPort: 9876
|
||||
selector:
|
||||
app: polaris
|
||||
component: webhook
|
||||
type: ClusterIP
|
||||
---
|
||||
# Source: polaris/templates/webhook.deployment.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: '6ec4a4dc87403cae67c01438398a5f1d4ef836ffeaf26a33b685c066b940495a'
|
||||
name: polaris-webhook
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
component: webhook
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: polaris
|
||||
component: webhook
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: polaris
|
||||
component: webhook
|
||||
spec:
|
||||
containers:
|
||||
- name: webhook
|
||||
command:
|
||||
- polaris
|
||||
- --webhook
|
||||
- --config
|
||||
- /opt/app/config.yaml
|
||||
image: 'quay.io/reactiveops/polaris:0.3'
|
||||
imagePullPolicy: 'Always'
|
||||
ports:
|
||||
- containerPort: 9876
|
||||
# These are fairly useless readiness/liveness probes for now
|
||||
# Follow this issue for potential improvements:
|
||||
# https://github.com/kubernetes-sigs/controller-runtime/issues/356
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- ps -ef | grep polaris
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- ps -ef | grep polaris
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /opt/app/config.yaml
|
||||
subPath: config.yaml
|
||||
readOnly: true
|
||||
- name: secret
|
||||
mountPath: /opt/cert/
|
||||
readOnly: true
|
||||
- name: cr-logs
|
||||
mountPath: /tmp/
|
||||
readOnly: false
|
||||
serviceAccountName: polaris-webhook
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: polaris
|
||||
- name: secret
|
||||
secret:
|
||||
secretName: polaris-webhook
|
||||
- name: cr-logs
|
||||
emptyDir: {}
|
||||
---
|
||||
# Source: polaris/templates/audit.job.yaml
|
||||
|
||||
---
|
||||
# Source: polaris/templates/audit.rbac.yaml
|
||||
|
||||
---
|
||||
# Source: polaris/templates/dashboard.deployment.yaml
|
||||
|
||||
---
|
||||
# Source: polaris/templates/dashboard.rbac.yaml
|
||||
|
||||
---
|
||||
# Source: polaris/templates/dashboard.service.yaml
|
||||
|
||||
---
|
||||
# Source: polaris/templates/ingress.yaml
|
||||
|
||||
@@ -42,7 +42,7 @@ import (
|
||||
|
||||
const (
|
||||
// Version represents the current release version of Polaris
|
||||
Version = "0.2.0"
|
||||
Version = "0.3.0"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -50,6 +50,8 @@ func main() {
|
||||
webhook := flag.Bool("webhook", false, "Runs the webhook webserver.")
|
||||
audit := flag.Bool("audit", false, "Runs a one-time audit.")
|
||||
auditPath := flag.String("audit-path", "", "If specified, audits one or more YAML files instead of a cluster")
|
||||
setExitCode := flag.Bool("set-exit-code-on-error", false, "When running with --audit, set an exit code of 3 when the audit contains error-level issues.")
|
||||
minScore := flag.Int("set-exit-code-below-score", 0, "When running with --audit, set an exit code of 4 when the score is below this threshold (1-100)")
|
||||
dashboardPort := flag.Int("dashboard-port", 8080, "Port for the dashboard webserver")
|
||||
dashboardBasePath := flag.String("dashboard-base-path", "/", "Path on which the dashboard is served")
|
||||
webhookPort := flag.Int("webhook-port", 9876, "Port for the webhook webserver")
|
||||
@@ -95,7 +97,14 @@ func main() {
|
||||
} else if *dashboard {
|
||||
startDashboardServer(c, *auditPath, *dashboardPort, *dashboardBasePath)
|
||||
} else if *audit {
|
||||
runAudit(c, *auditPath, *auditOutputFile, *auditOutputURL, *auditOutputFormat)
|
||||
auditData := runAndReportAudit(c, *auditPath, *auditOutputFile, *auditOutputURL, *auditOutputFormat)
|
||||
if *setExitCode && auditData.ClusterSummary.Results.Totals.Errors > 0 {
|
||||
logrus.Infof("%d errors found in audit", auditData.ClusterSummary.Results.Totals.Errors)
|
||||
os.Exit(3)
|
||||
} else if *minScore != 0 && auditData.ClusterSummary.Score < uint(*minScore) {
|
||||
logrus.Infof("Audit score of %d is less than the provided minimum of %d", auditData.ClusterSummary.Score, *minScore)
|
||||
os.Exit(4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +189,7 @@ func startWebhookServer(c conf.Configuration, disableWebhookConfigInstaller bool
|
||||
}
|
||||
}
|
||||
|
||||
func runAudit(c conf.Configuration, auditPath string, outputFile string, outputURL string, outputFormat string) {
|
||||
func runAndReportAudit(c conf.Configuration, auditPath string, outputFile string, outputURL string, outputFormat string) validator.AuditData {
|
||||
k, err := kube.CreateResourceProvider(auditPath)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Kubernetes resources %v", err)
|
||||
@@ -194,7 +203,7 @@ func runAudit(c conf.Configuration, auditPath string, outputFile string, outputU
|
||||
|
||||
var outputBytes []byte
|
||||
if outputFormat == "score" {
|
||||
outputBytes = []byte(fmt.Sprint(auditData.ClusterSummary.Score))
|
||||
outputBytes = []byte(fmt.Sprintf("%d\n", auditData.ClusterSummary.Score))
|
||||
} else if outputFormat == "yaml" {
|
||||
jsonBytes, err := json.Marshal(auditData)
|
||||
if err == nil {
|
||||
@@ -253,4 +262,5 @@ func runAudit(c conf.Configuration, auditPath string, outputFile string, outputU
|
||||
}
|
||||
}
|
||||
}
|
||||
return auditData
|
||||
}
|
||||
|
||||
+59
-13
@@ -29,16 +29,34 @@ type ContainerValidation struct {
|
||||
*ResourceValidation
|
||||
Container *corev1.Container
|
||||
IsInitContainer bool
|
||||
parentPodSpec corev1.PodSpec
|
||||
}
|
||||
|
||||
// ValidateContainer validates that each pod conforms to the Polaris config, returns a ResourceResult.
|
||||
func ValidateContainer(cnConf *conf.Configuration, container *corev1.Container, isInit bool) ContainerResult {
|
||||
// FIXME When validating a container, there are some things in a container spec
|
||||
// that can be affected by the podSpec. This means we need a copy of the
|
||||
// relevant podSpec in order to check certain aspects of a containerSpec.
|
||||
// Perhaps there is a more ideal solution instead of attaching a parent
|
||||
// podSpec to every container Validation struct...
|
||||
func ValidateContainer(container *corev1.Container, parentPodResult *PodResult, cnConf *conf.Configuration, isInit bool) ContainerResult {
|
||||
cv := ContainerValidation{
|
||||
Container: container,
|
||||
ResourceValidation: &ResourceValidation{},
|
||||
IsInitContainer: isInit,
|
||||
}
|
||||
|
||||
// Support initializing
|
||||
// FIXME This is a product of pulling in the podSpec, ideally we'd never
|
||||
// expect this be nil but our tests have conditions in which the
|
||||
// parent podResult isn't initialized in this ContainerValidation
|
||||
// struct.
|
||||
if parentPodResult == nil {
|
||||
// initialize a blank pod spec
|
||||
cv.parentPodSpec = corev1.PodSpec{}
|
||||
} else {
|
||||
cv.parentPodSpec = parentPodResult.podSpec
|
||||
}
|
||||
|
||||
cv.validateResources(&cnConf.Resources)
|
||||
cv.validateHealthChecks(&cnConf.HealthChecks)
|
||||
cv.validateImage(&cnConf.Images)
|
||||
@@ -176,39 +194,58 @@ func (cv *ContainerValidation) validateNetworking(networkConf *conf.Networking)
|
||||
func (cv *ContainerValidation) validateSecurity(securityConf *conf.Security) {
|
||||
category := messages.CategorySecurity
|
||||
securityContext := cv.Container.SecurityContext
|
||||
podSecurityContext := cv.parentPodSpec.SecurityContext
|
||||
|
||||
// Support an empty container security context
|
||||
if securityContext == nil {
|
||||
securityContext = &corev1.SecurityContext{}
|
||||
}
|
||||
|
||||
// Support an empty pod security context
|
||||
if podSecurityContext == nil {
|
||||
podSecurityContext = &corev1.PodSecurityContext{}
|
||||
}
|
||||
|
||||
if securityConf.RunAsRootAllowed.IsActionable() {
|
||||
if securityContext.RunAsNonRoot == (*bool)(nil) || !*securityContext.RunAsNonRoot {
|
||||
cv.addFailure(messages.RunAsRootFailure, securityConf.RunAsRootAllowed, category)
|
||||
} else {
|
||||
if getBoolValue(securityContext.RunAsNonRoot) {
|
||||
// Check if the container is explicitly set to True (pass)
|
||||
cv.addSuccess(messages.RunAsRootSuccess, category)
|
||||
} else if securityContext.RunAsNonRoot == nil {
|
||||
// Check if the value in the container spec if nil (thus defaulting to the podspec)
|
||||
// Check if the container value is not set
|
||||
if getBoolValue(podSecurityContext.RunAsNonRoot) {
|
||||
// if the pod spec default for containers is true, then pass
|
||||
cv.addSuccess(messages.RunAsRootSuccess, category)
|
||||
} else {
|
||||
// else fail as RunAsNonRoot defaults to false
|
||||
cv.addFailure(messages.RunAsRootFailure, securityConf.RunAsRootAllowed, category)
|
||||
}
|
||||
} else {
|
||||
cv.addFailure(messages.RunAsRootFailure, securityConf.RunAsRootAllowed, category)
|
||||
}
|
||||
}
|
||||
|
||||
if securityConf.RunAsPrivileged.IsActionable() {
|
||||
if securityContext.Privileged == (*bool)(nil) || !*securityContext.Privileged {
|
||||
cv.addSuccess(messages.RunAsPrivilegedSuccess, category)
|
||||
} else {
|
||||
if getBoolValue(securityContext.Privileged) {
|
||||
cv.addFailure(messages.RunAsPrivilegedFailure, securityConf.RunAsPrivileged, category)
|
||||
} else {
|
||||
cv.addSuccess(messages.RunAsPrivilegedSuccess, category)
|
||||
}
|
||||
}
|
||||
|
||||
if securityConf.NotReadOnlyRootFileSystem.IsActionable() {
|
||||
if securityContext.ReadOnlyRootFilesystem == (*bool)(nil) || !*securityContext.ReadOnlyRootFilesystem {
|
||||
cv.addFailure(messages.ReadOnlyFilesystemFailure, securityConf.NotReadOnlyRootFileSystem, category)
|
||||
} else {
|
||||
if getBoolValue(securityContext.ReadOnlyRootFilesystem) {
|
||||
cv.addSuccess(messages.ReadOnlyFilesystemSuccess, category)
|
||||
} else {
|
||||
cv.addFailure(messages.ReadOnlyFilesystemFailure, securityConf.NotReadOnlyRootFileSystem, category)
|
||||
}
|
||||
}
|
||||
|
||||
if securityConf.PrivilegeEscalationAllowed.IsActionable() {
|
||||
if securityContext.AllowPrivilegeEscalation == (*bool)(nil) || !*securityContext.AllowPrivilegeEscalation {
|
||||
cv.addSuccess(messages.PrivilegeEscalationSuccess, category)
|
||||
} else {
|
||||
if getBoolValue(securityContext.AllowPrivilegeEscalation) {
|
||||
cv.addFailure(messages.PrivilegeEscalationFailure, securityConf.PrivilegeEscalationAllowed, category)
|
||||
} else {
|
||||
cv.addSuccess(messages.PrivilegeEscalationSuccess, category)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,3 +360,12 @@ func capContains(list []corev1.Capability, val corev1.Capability) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// getBoolValue returns false if nil or returns the value of the bool pointer
|
||||
func getBoolValue(val *bool) bool {
|
||||
if val == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return *val
|
||||
}
|
||||
|
||||
@@ -567,6 +567,42 @@ func TestValidateSecurity(t *testing.T) {
|
||||
ResourceValidation: &ResourceValidation{},
|
||||
}
|
||||
|
||||
badCVWithGoodPodSpec := ContainerValidation{
|
||||
Container: &corev1.Container{Name: "", SecurityContext: &corev1.SecurityContext{
|
||||
RunAsNonRoot: &falseVar,
|
||||
ReadOnlyRootFilesystem: &falseVar,
|
||||
Privileged: &trueVar,
|
||||
AllowPrivilegeEscalation: &trueVar,
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Add: []corev1.Capability{"AUDIT_CONTROL", "SYS_ADMIN", "NET_ADMIN"},
|
||||
},
|
||||
}},
|
||||
ResourceValidation: &ResourceValidation{},
|
||||
parentPodSpec: corev1.PodSpec{
|
||||
SecurityContext: &corev1.PodSecurityContext{
|
||||
RunAsNonRoot: &trueVar,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
badCVWithBadPodSpec := ContainerValidation{
|
||||
Container: &corev1.Container{Name: "", SecurityContext: &corev1.SecurityContext{
|
||||
RunAsNonRoot: nil, // this will use the default from the podspec
|
||||
ReadOnlyRootFilesystem: &falseVar,
|
||||
Privileged: &trueVar,
|
||||
AllowPrivilegeEscalation: &trueVar,
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Add: []corev1.Capability{"AUDIT_CONTROL", "SYS_ADMIN", "NET_ADMIN"},
|
||||
},
|
||||
}},
|
||||
ResourceValidation: &ResourceValidation{},
|
||||
parentPodSpec: corev1.PodSpec{
|
||||
SecurityContext: &corev1.PodSecurityContext{
|
||||
RunAsNonRoot: &falseVar,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
goodCV := ContainerValidation{
|
||||
Container: &corev1.Container{Name: "", SecurityContext: &corev1.SecurityContext{
|
||||
RunAsNonRoot: &trueVar,
|
||||
@@ -593,6 +629,42 @@ func TestValidateSecurity(t *testing.T) {
|
||||
ResourceValidation: &ResourceValidation{},
|
||||
}
|
||||
|
||||
strongCVWithPodSpecSecurityContext := ContainerValidation{
|
||||
Container: &corev1.Container{Name: "", SecurityContext: &corev1.SecurityContext{
|
||||
RunAsNonRoot: nil, // not set but overridden via podSpec
|
||||
ReadOnlyRootFilesystem: &trueVar,
|
||||
Privileged: &falseVar,
|
||||
AllowPrivilegeEscalation: &falseVar,
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Drop: []corev1.Capability{"ALL"},
|
||||
},
|
||||
}},
|
||||
ResourceValidation: &ResourceValidation{},
|
||||
parentPodSpec: corev1.PodSpec{
|
||||
SecurityContext: &corev1.PodSecurityContext{
|
||||
RunAsNonRoot: &trueVar,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
strongCVWithBadPodSpecSecurityContext := ContainerValidation{
|
||||
Container: &corev1.Container{Name: "", SecurityContext: &corev1.SecurityContext{
|
||||
RunAsNonRoot: &trueVar, // will override the bad setting in PodSpec
|
||||
ReadOnlyRootFilesystem: &trueVar,
|
||||
Privileged: &falseVar,
|
||||
AllowPrivilegeEscalation: &falseVar,
|
||||
Capabilities: &corev1.Capabilities{
|
||||
Drop: []corev1.Capability{"ALL"},
|
||||
},
|
||||
}},
|
||||
ResourceValidation: &ResourceValidation{},
|
||||
parentPodSpec: corev1.PodSpec{
|
||||
SecurityContext: &corev1.PodSecurityContext{
|
||||
RunAsNonRoot: &falseVar, // is overridden at container level with RunAsNonRoot:true
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var testCases = []struct {
|
||||
name string
|
||||
securityConf conf.Security
|
||||
@@ -661,6 +733,66 @@ func TestValidateSecurity(t *testing.T) {
|
||||
Category: "Security",
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "bad security context + standard validation config with good settings in podspec",
|
||||
securityConf: standardConf,
|
||||
cv: badCVWithGoodPodSpec,
|
||||
expectedMessages: []*ResultMessage{{
|
||||
Message: "The following security capabilities should not be added: SYS_ADMIN, NET_ADMIN",
|
||||
Type: "error",
|
||||
Category: "Security",
|
||||
}, {
|
||||
Message: "Privilege escalation should not be allowed",
|
||||
Type: "error",
|
||||
Category: "Security",
|
||||
}, {
|
||||
Message: "Should not be running as privileged",
|
||||
Type: "error",
|
||||
Category: "Security",
|
||||
}, {
|
||||
Message: "The following security capabilities should not be added: AUDIT_CONTROL, SYS_ADMIN, NET_ADMIN",
|
||||
Type: "warning",
|
||||
Category: "Security",
|
||||
}, {
|
||||
Message: "Should not be allowed to run as root",
|
||||
Type: "warning",
|
||||
Category: "Security",
|
||||
}, {
|
||||
Message: "Filesystem should be read only",
|
||||
Type: "warning",
|
||||
Category: "Security",
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "bad security context + standard validation config from default set in podspec",
|
||||
securityConf: standardConf,
|
||||
cv: badCVWithBadPodSpec,
|
||||
expectedMessages: []*ResultMessage{{
|
||||
Message: "The following security capabilities should not be added: SYS_ADMIN, NET_ADMIN",
|
||||
Type: "error",
|
||||
Category: "Security",
|
||||
}, {
|
||||
Message: "Privilege escalation should not be allowed",
|
||||
Type: "error",
|
||||
Category: "Security",
|
||||
}, {
|
||||
Message: "Should not be running as privileged",
|
||||
Type: "error",
|
||||
Category: "Security",
|
||||
}, {
|
||||
Message: "The following security capabilities should not be added: AUDIT_CONTROL, SYS_ADMIN, NET_ADMIN",
|
||||
Type: "warning",
|
||||
Category: "Security",
|
||||
}, {
|
||||
Message: "Should not be allowed to run as root",
|
||||
Type: "warning",
|
||||
Category: "Security",
|
||||
}, {
|
||||
Message: "Filesystem should be read only",
|
||||
Type: "warning",
|
||||
Category: "Security",
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "good security context + standard validation config",
|
||||
securityConf: standardConf,
|
||||
@@ -739,6 +871,58 @@ func TestValidateSecurity(t *testing.T) {
|
||||
Category: "Security",
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "strong security context + strong validation config via podspec default",
|
||||
securityConf: strongConf,
|
||||
cv: strongCVWithPodSpecSecurityContext,
|
||||
expectedMessages: []*ResultMessage{{
|
||||
Message: "Is not allowed to run as root",
|
||||
Type: "success",
|
||||
Category: "Security",
|
||||
}, {
|
||||
Message: "Filesystem is read only",
|
||||
Type: "success",
|
||||
Category: "Security",
|
||||
}, {
|
||||
Message: "Not running as privileged",
|
||||
Type: "success",
|
||||
Category: "Security",
|
||||
}, {
|
||||
Message: "Privilege escalation not allowed",
|
||||
Type: "success",
|
||||
Category: "Security",
|
||||
}, {
|
||||
Message: "Security capabilities are within the configured limits",
|
||||
Type: "success",
|
||||
Category: "Security",
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "strong security context + strong validation config with bad setting in podspec default",
|
||||
securityConf: strongConf,
|
||||
cv: strongCVWithBadPodSpecSecurityContext,
|
||||
expectedMessages: []*ResultMessage{{
|
||||
Message: "Is not allowed to run as root",
|
||||
Type: "success",
|
||||
Category: "Security",
|
||||
}, {
|
||||
Message: "Filesystem is read only",
|
||||
Type: "success",
|
||||
Category: "Security",
|
||||
}, {
|
||||
Message: "Not running as privileged",
|
||||
Type: "success",
|
||||
Category: "Security",
|
||||
}, {
|
||||
Message: "Privilege escalation not allowed",
|
||||
Type: "success",
|
||||
Category: "Security",
|
||||
}, {
|
||||
Message: "Security capabilities are within the configured limits",
|
||||
Type: "success",
|
||||
Category: "Security",
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
|
||||
@@ -40,6 +40,7 @@ func ValidatePod(podConf conf.Configuration, pod *corev1.PodSpec) PodResult {
|
||||
Messages: pv.messages(),
|
||||
ContainerResults: []ContainerResult{},
|
||||
Summary: pv.summary(),
|
||||
podSpec: *pod,
|
||||
}
|
||||
|
||||
pv.validateContainers(pod.InitContainers, &pRes, &podConf, true)
|
||||
@@ -54,7 +55,7 @@ func ValidatePod(podConf conf.Configuration, pod *corev1.PodSpec) PodResult {
|
||||
|
||||
func (pv *PodValidation) validateContainers(containers []corev1.Container, pRes *PodResult, podConf *conf.Configuration, isInit bool) {
|
||||
for _, container := range containers {
|
||||
cRes := ValidateContainer(podConf, &container, isInit)
|
||||
cRes := ValidateContainer(&container, pRes, podConf, isInit)
|
||||
pRes.ContainerResults = append(pRes.ContainerResults, cRes)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
package validator
|
||||
|
||||
import corev1 "k8s.io/api/core/v1"
|
||||
|
||||
// MessageType represents the type of Message
|
||||
type MessageType string
|
||||
|
||||
@@ -116,6 +118,7 @@ type PodResult struct {
|
||||
Summary *ResultSummary
|
||||
Messages []*ResultMessage
|
||||
ContainerResults []ContainerResult
|
||||
podSpec corev1.PodSpec
|
||||
}
|
||||
|
||||
// ResultMessage contains a message and a type indicator (success, warning, or error).
|
||||
|
||||
Reference in New Issue
Block a user