mirror of
https://github.com/FairwindsOps/polaris.git
synced 2026-08-29 22:17:16 +00:00
Compare commits
35
Commits
@@ -27,24 +27,22 @@ references:
|
||||
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
|
||||
curl -sLO https://github.com/kubernetes-sigs/kind/releases/download/v0.8.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
|
||||
curl -sLO https://storage.googleapis.com/kubernetes-release/release/v1.18.6/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
|
||||
kind create cluster --wait=90s --image kindest/node:v1.15.11
|
||||
docker ps -a
|
||||
|
||||
echo "Setting up kubecfg"
|
||||
cp $(kind get kubeconfig-path --name=kind) ~/.kube/config
|
||||
kubectl version
|
||||
|
||||
# Test scripts
|
||||
|
||||
+3
-1
@@ -21,4 +21,6 @@ main
|
||||
|
||||
*-packr.go
|
||||
dist
|
||||
.vscode
|
||||
.vscode
|
||||
|
||||
*-test.yaml
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# x.x.x (next release)
|
||||
|
||||
# 1.2.1
|
||||
* Update date on dashboard footer
|
||||
|
||||
# 1.2.0
|
||||
* Add ability to audit a single workload
|
||||
* Enable `pullPolicyAlways` by default
|
||||
* Fix for finding parent resources
|
||||
|
||||
# 1.1.1
|
||||
* Show controller checks on dashboard
|
||||
* Fix for orphaned pods w/ controller checks
|
||||
|
||||
@@ -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=1.1.1&color=239922
|
||||
[version-image]: https://img.shields.io/static/v1.svg?label=Version&message=1.2.0&color=239922
|
||||
[version-link]: https://github.com/FairwindsOps/polaris
|
||||
|
||||
[goreport-image]: https://goreportcard.com/badge/github.com/FairwindsOps/polaris
|
||||
|
||||
@@ -16,6 +16,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
@@ -35,6 +36,7 @@ var minScore int
|
||||
var auditOutputURL string
|
||||
var auditOutputFile string
|
||||
var auditOutputFormat string
|
||||
var resourceToAudit string
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(auditCmd)
|
||||
@@ -45,6 +47,7 @@ func init() {
|
||||
auditCmd.PersistentFlags().StringVar(&auditOutputFile, "output-file", "", "Destination file for audit results.")
|
||||
auditCmd.PersistentFlags().StringVarP(&auditOutputFormat, "format", "f", "json", "Output format for results - json, yaml, or score.")
|
||||
auditCmd.PersistentFlags().StringVar(&displayName, "display-name", "", "An optional identifier for the audit.")
|
||||
auditCmd.PersistentFlags().StringVar(&resourceToAudit, "resource", "", "Audit a specific resource, in the format namespace/kind/version/name, e.g. nginx-ingress/Deployment.apps/v1/default-backend.")
|
||||
}
|
||||
|
||||
var auditCmd = &cobra.Command{
|
||||
@@ -56,7 +59,7 @@ var auditCmd = &cobra.Command{
|
||||
config.DisplayName = displayName
|
||||
}
|
||||
|
||||
auditData := runAndReportAudit(config, auditPath, auditOutputFile, auditOutputURL, auditOutputFormat)
|
||||
auditData := runAndReportAudit(cmd.Context(), config, auditPath, resourceToAudit, auditOutputFile, auditOutputURL, auditOutputFormat)
|
||||
|
||||
summary := auditData.GetSummary()
|
||||
score := summary.GetScore()
|
||||
@@ -70,14 +73,14 @@ var auditCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
func runAndReportAudit(c conf.Configuration, auditPath string, outputFile string, outputURL string, outputFormat string) validator.AuditData {
|
||||
func runAndReportAudit(ctx context.Context, c conf.Configuration, auditPath, workload, outputFile, outputURL, outputFormat string) validator.AuditData {
|
||||
// Create a kubernetes client resource provider
|
||||
k, err := kube.CreateResourceProvider(auditPath)
|
||||
k, err := kube.CreateResourceProvider(ctx, auditPath, workload)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Kubernetes resources %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
auditData, err := validator.RunAudit(c, k)
|
||||
auditData, err := validator.RunAudit(ctx, c, k)
|
||||
|
||||
if err != nil {
|
||||
logrus.Errorf("Error while running audit on resources: %v", err)
|
||||
|
||||
+17
-100
@@ -15,50 +15,18 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
fwebhook "github.com/fairwindsops/polaris/pkg/webhook"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
appsv1beta1 "k8s.io/api/apps/v1beta1"
|
||||
appsv1beta2 "k8s.io/api/apps/v1beta2"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
batchv1beta1 "k8s.io/api/batch/v1beta1"
|
||||
batchv2alpha1 "k8s.io/api/batch/v2alpha1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
apitypes "k8s.io/apimachinery/pkg/types"
|
||||
|
||||
fwebhook "github.com/fairwindsops/polaris/pkg/webhook"
|
||||
k8sConfig "sigs.k8s.io/controller-runtime/pkg/client/config"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/runtime/signals"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
||||
)
|
||||
|
||||
var supportedVersions = map[string]runtime.Object{
|
||||
"appsv1/Deployment": &appsv1.Deployment{},
|
||||
"appsv1beta1/Deployment": &appsv1beta1.Deployment{},
|
||||
"appsv1beta2/Deployment": &appsv1beta2.Deployment{},
|
||||
|
||||
"appsv1/StatefulSet": &appsv1.StatefulSet{},
|
||||
"appsv1beta1/StatefulSet": &appsv1beta1.StatefulSet{},
|
||||
"appsv1beta2/StatefulSet": &appsv1beta2.StatefulSet{},
|
||||
|
||||
"appsv1/DaemonSet": &appsv1.DaemonSet{},
|
||||
"appsv1beta2/DaemonSet": &appsv1beta2.DaemonSet{},
|
||||
|
||||
"batchv1/Job": &batchv1.Job{},
|
||||
|
||||
"batchv1beta1/CronJob": &batchv1beta1.CronJob{},
|
||||
"batchv2alpha1/CronJob": &batchv2alpha1.CronJob{},
|
||||
|
||||
"corev1/ReplicationController": &corev1.ReplicationController{},
|
||||
|
||||
"corev1/Pod": &corev1.Pod{},
|
||||
}
|
||||
|
||||
var webhookPort int
|
||||
var disableWebhookConfigInstaller bool
|
||||
|
||||
@@ -74,81 +42,30 @@ var webhookCmd = &cobra.Command{
|
||||
Long: `Runs the webhook webserver.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
logrus.Debug("Setting up controller manager")
|
||||
mgr, err := manager.New(k8sConfig.GetConfigOrDie(), manager.Options{})
|
||||
|
||||
mgr, err := manager.New(k8sConfig.GetConfigOrDie(), manager.Options{
|
||||
CertDir: "/opt/cert",
|
||||
Port: webhookPort,
|
||||
})
|
||||
if err != nil {
|
||||
logrus.Errorf("Unable to set up overall controller manager: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
polarisAppName := "polaris"
|
||||
polarisResourceName := "polaris-webhook"
|
||||
polarisNamespaceBytes, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
|
||||
|
||||
if err != nil {
|
||||
// Not exiting here as we have fallback options
|
||||
logrus.Debugf("Error reading namespace information: %v", err)
|
||||
_, err = os.Stat("/opt/cert/tls.crt")
|
||||
if os.IsNotExist(err) {
|
||||
time.Sleep(time.Second * 10)
|
||||
panic("Cert does not exist")
|
||||
}
|
||||
|
||||
polarisNamespace := string(polarisNamespaceBytes)
|
||||
if polarisNamespace == "" {
|
||||
polarisNamespace = polarisResourceName
|
||||
logrus.Debugf("Could not determine current namespace, creating resources in %s namespace", polarisNamespace)
|
||||
}
|
||||
|
||||
logrus.Info("Setting up webhook server")
|
||||
as, err := webhook.NewServer(polarisResourceName, mgr, webhook.ServerOptions{
|
||||
Port: int32(webhookPort),
|
||||
CertDir: "/opt/cert",
|
||||
DisableWebhookConfigInstaller: &disableWebhookConfigInstaller,
|
||||
BootstrapOptions: &webhook.BootstrapOptions{
|
||||
ValidatingWebhookConfigName: polarisResourceName,
|
||||
Secret: &apitypes.NamespacedName{
|
||||
Namespace: polarisNamespace,
|
||||
Name: polarisResourceName,
|
||||
},
|
||||
|
||||
Service: &webhook.Service{
|
||||
Namespace: polarisNamespace,
|
||||
Name: polarisResourceName,
|
||||
|
||||
// Selectors should select the pods that runs this webhook server.
|
||||
Selectors: map[string]string{
|
||||
"app": polarisAppName,
|
||||
"component": "webhook",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logrus.Errorf("Error setting up webhook server: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logrus.Infof("Polaris webhook server listening on port %d", webhookPort)
|
||||
server := mgr.GetWebhookServer()
|
||||
server.CertName = "tls.crt"
|
||||
server.KeyName = "tls.key"
|
||||
|
||||
// Iterate all the configurations supported controllers to scan and register them for webhooks
|
||||
// Should only register controllers that are configured to be scanned
|
||||
logrus.Debug("Registering webhooks to the webhook server")
|
||||
var webhooks []webhook.Webhook
|
||||
for name, supportedAPIType := range supportedVersions {
|
||||
webhookName := strings.ToLower(name)
|
||||
webhookName = strings.ReplaceAll(webhookName, "/", "-")
|
||||
hook, err := fwebhook.NewWebhook(webhookName, mgr, fwebhook.Validator{Config: config}, supportedAPIType)
|
||||
if err != nil {
|
||||
logrus.Warningf("Couldn't build webhook %s: %v", webhookName, err)
|
||||
continue
|
||||
}
|
||||
webhooks = append(webhooks, hook)
|
||||
logrus.Infof("%s webhook started", webhookName)
|
||||
}
|
||||
fwebhook.NewWebhook(mgr, fwebhook.Validator{Config: config, Client: mgr.GetClient()})
|
||||
|
||||
if err = as.Register(webhooks...); err != nil {
|
||||
logrus.Debugf("Unable to register webhooks in the admission server: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logrus.Debug("Starting webhook manager")
|
||||
logrus.Infof("Polaris webhook server listening on port %d", webhookPort)
|
||||
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
|
||||
logrus.Errorf("Error starting manager: %v", err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -109,7 +109,7 @@ spec:
|
||||
- command:
|
||||
- polaris
|
||||
- dashboard
|
||||
image: 'quay.io/fairwinds/polaris:1.1'
|
||||
image: 'quay.io/fairwinds/polaris:1.2'
|
||||
imagePullPolicy: 'Always'
|
||||
name: dashboard
|
||||
ports:
|
||||
|
||||
+158
-8
@@ -14,6 +14,15 @@ metadata:
|
||||
labels:
|
||||
app: polaris
|
||||
---
|
||||
# Source: polaris/templates/webhook.rbac.yaml
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: polaris-certificates
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
---
|
||||
# Source: polaris/templates/webhook.secret.yaml
|
||||
# The name of this secret is static as it is populated by the webhook pod.
|
||||
apiVersion: v1
|
||||
@@ -23,9 +32,10 @@ metadata:
|
||||
namespace: polaris
|
||||
labels:
|
||||
app: polaris
|
||||
type: Opaque
|
||||
stringData:
|
||||
cert.pem: ''
|
||||
type: kubernetes.io/tls
|
||||
data:
|
||||
tls.crt: ''
|
||||
tls.key: ''
|
||||
---
|
||||
# Source: polaris/templates/rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
@@ -53,8 +63,6 @@ metadata:
|
||||
labels:
|
||||
app: polaris
|
||||
rules:
|
||||
# required by controller-runtime code doing a cluster wide lookup
|
||||
# when it seems namespace would suffice
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
@@ -64,6 +72,24 @@ rules:
|
||||
- 'get'
|
||||
- 'list'
|
||||
- 'watch'
|
||||
- apiGroups:
|
||||
- 'certificates.k8s.io'
|
||||
resources:
|
||||
- 'certificatesigningrequests'
|
||||
- 'certificatesigningrequests/approval'
|
||||
verbs:
|
||||
- 'get'
|
||||
- 'update'
|
||||
- 'create'
|
||||
- 'delete'
|
||||
- apiGroups:
|
||||
- 'certificates.k8s.io'
|
||||
resources:
|
||||
- 'signers'
|
||||
resourceNames:
|
||||
- 'kubernetes.io/legacy-unknown'
|
||||
verbs:
|
||||
- 'approve'
|
||||
- apiGroups:
|
||||
- 'admissionregistration.k8s.io'
|
||||
resources:
|
||||
@@ -116,7 +142,7 @@ roleRef:
|
||||
name: polaris-webhook
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: polaris
|
||||
name: polaris-certificates
|
||||
namespace: polaris
|
||||
---
|
||||
# Source: polaris/templates/webhook.rbac.yaml
|
||||
@@ -151,7 +177,7 @@ roleRef:
|
||||
name: polaris-webhook
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: polaris
|
||||
name: polaris-certificates
|
||||
namespace: polaris
|
||||
---
|
||||
# Source: polaris/templates/webhook.service.yaml
|
||||
@@ -199,7 +225,7 @@ spec:
|
||||
command:
|
||||
- polaris
|
||||
- webhook
|
||||
image: 'quay.io/fairwinds/polaris:1.1'
|
||||
image: 'quay.io/fairwinds/polaris:1.2'
|
||||
imagePullPolicy: 'Always'
|
||||
ports:
|
||||
- containerPort: 9876
|
||||
@@ -253,3 +279,127 @@ spec:
|
||||
secretName: polaris-webhook
|
||||
- name: cr-logs
|
||||
emptyDir: {}
|
||||
---
|
||||
# Source: polaris/templates/webhook.job.yaml
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: polaris-certificate-updater
|
||||
namespace: polaris
|
||||
labels:
|
||||
|
||||
app: polaris
|
||||
component: certificate-updater
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
|
||||
app: polaris
|
||||
component: certificate-updater
|
||||
spec:
|
||||
containers:
|
||||
- name: webhook-certificate-generator
|
||||
image: 'newrelic/k8s-webhook-cert-manager:1.3.0'
|
||||
imagePullPolicy: Always
|
||||
command:
|
||||
- ./generate_certificate.sh
|
||||
- --service
|
||||
- polaris-webhook
|
||||
- --namespace
|
||||
- polaris
|
||||
- --secret
|
||||
- polaris-webhook
|
||||
- --webhook
|
||||
- polaris-webhook
|
||||
- --webhook-kind
|
||||
- ValidatingWebhookConfiguration
|
||||
resources:
|
||||
limits:
|
||||
cpu: 150m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
volumeMounts:
|
||||
- name: tmp
|
||||
mountPath: /tmp/
|
||||
readOnly: false
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
serviceAccountName: polaris-certificates
|
||||
restartPolicy: Never
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
---
|
||||
# Source: polaris/templates/webhook.configuration.yaml
|
||||
apiVersion: admissionregistration.k8s.io/v1beta1
|
||||
kind: ValidatingWebhookConfiguration
|
||||
metadata:
|
||||
name: polaris-webhook
|
||||
webhooks:
|
||||
- admissionReviewVersions:
|
||||
- v1beta1
|
||||
clientConfig:
|
||||
caBundle: ""
|
||||
service:
|
||||
name: polaris-webhook
|
||||
namespace: polaris
|
||||
path: /validate
|
||||
port: 443
|
||||
failurePolicy: Ignore
|
||||
matchPolicy: Exact
|
||||
name: polaris.fairwinds.com
|
||||
namespaceSelector:
|
||||
matchExpressions:
|
||||
- key: control-plane
|
||||
operator: DoesNotExist
|
||||
objectSelector: {}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- apps
|
||||
apiVersions:
|
||||
- v1
|
||||
- v1beta1
|
||||
- v1beta2
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- daemonsets
|
||||
- deployments
|
||||
- statefulsets
|
||||
scope: Namespaced
|
||||
- apiGroups:
|
||||
- batch
|
||||
apiVersions:
|
||||
- v1
|
||||
- v1beta1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- jobs
|
||||
- cronjobs
|
||||
scope: Namespaced
|
||||
- apiGroups:
|
||||
- ""
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- pods
|
||||
- replicationcontrollers
|
||||
scope: Namespaced
|
||||
sideEffects: None
|
||||
timeoutSeconds: 30
|
||||
|
||||
@@ -5,13 +5,13 @@ Polaris supports a number of checks related to the image specified by pods.
|
||||
key | default | description
|
||||
----|---------|------------
|
||||
`images.tagNotSpecified` | `danger` | Fails when an image tag is either not specified or `latest`.
|
||||
`images.pullPolicyNotAlways` | `ignore` | Fails when an image pull policy is not `always`.
|
||||
`images.pullPolicyNotAlways` | `warning` | Fails when an image pull policy is not `always`.
|
||||
|
||||
## Background
|
||||
|
||||
Docker's `latest` tag is applied by default to images where a tag hasn't been specified. Not specifying a specific version of an image can lead to a wide variety of problems. The underlying image could include unexpected breaking changes that break your application whenever the latest image is pulled. Reusing the same tag for multiple versions of an image can lead to different nodes in the same cluster having different versions of an image, even if the tag is identical.
|
||||
|
||||
Related to that, relying on cached versions of a Docker image can become a security vulnerability. By default, an image will be pulled if it isn't already cached on the node attempting to run it. This can result in variations in images that are running per node, or potentially provide a way to gain access to an image without having direct access to the ImagePullSecret. With that in mind, it's often better to ensure the a pod has `pullPolicy: Always` specified, so images are always pulled directly from their source. This is not a check enabled by default with Polaris as organizations may not wish to add the overhead involved with pulling images for each pod.
|
||||
Related to that, relying on cached versions of a Docker image can become a security vulnerability. By default, an image will be pulled if it isn't already cached on the node attempting to run it. This can result in variations in images that are running per node, or potentially provide a way to gain access to an image without having direct access to the ImagePullSecret. With that in mind, it's often better to ensure the a pod has `pullPolicy: Always` specified, so images are always pulled directly from their source.
|
||||
|
||||
## Further Reading
|
||||
|
||||
|
||||
+23
-4
@@ -115,6 +115,17 @@ brew install reactiveops/tap/polaris
|
||||
polaris dashboard --port 8080
|
||||
```
|
||||
|
||||
You can also point the dashboard to the local filesystem, instead of a live cluster:
|
||||
```bash
|
||||
polaris dashboard --port 8080 --audit-path=./deploy/
|
||||
```
|
||||
|
||||
### Local Docker container
|
||||
|
||||
```
|
||||
docker run -d -p8080:8080 -v ~/.kube/config:/opt/app/config:ro quay.io/fairwinds/polaris:1.2 polaris dashboard --kubeconfig /opt/app/config
|
||||
```
|
||||
|
||||
## Webhook
|
||||
### kubectl
|
||||
```bash
|
||||
@@ -145,13 +156,19 @@ polaris audit --format score
|
||||
# 92
|
||||
```
|
||||
|
||||
Both the dashboard and audits can run against a local directory or YAML file
|
||||
rather than a cluster:
|
||||
Audits can run against a local directory or YAML file rather than a cluster:
|
||||
```bash
|
||||
polaris audit --audit-path ./deploy/
|
||||
|
||||
# or to use STDIN
|
||||
cat pod.yaml | polaris audit --audit-path -
|
||||
```
|
||||
|
||||
You can also run the audit on a single resource instead of the entire cluster:
|
||||
```bash
|
||||
polaris audit --resource "nginx-ingress/Deployment.apps/v1/default-backend"
|
||||
```
|
||||
|
||||
#### Running with CI/CD
|
||||
You can integrate Polaris into CI/CD for repositories containing infrastructure-as-code.
|
||||
For example, to fail if polaris detects *any* danger-level issues, or if the score drops below 90%:
|
||||
@@ -207,12 +224,14 @@ webhook
|
||||
# audit flags
|
||||
--audit-path string
|
||||
If specified, audits one or more YAML files instead of a cluster
|
||||
--resource string
|
||||
If specified, audit a specific resource, in the format namespace/kind/version/name, e.g. nginx-ingress/Deployment.apps/v1/default-backend
|
||||
--display-name string
|
||||
An optional identifier for the audit
|
||||
--format string
|
||||
Output format for results - json, yaml, or score (default "json")
|
||||
--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
|
||||
|
||||
@@ -9,7 +9,7 @@ checks:
|
||||
memoryLimitsMissing: warning
|
||||
# images
|
||||
tagNotSpecified: danger
|
||||
pullPolicyNotAlways: ignore
|
||||
pullPolicyNotAlways: warning
|
||||
# healthChecks
|
||||
readinessProbeMissing: warning
|
||||
livenessProbeMissing: warning
|
||||
|
||||
@@ -9,7 +9,7 @@ checks:
|
||||
memoryLimitsMissing: warning
|
||||
# images
|
||||
tagNotSpecified: danger
|
||||
pullPolicyNotAlways: ignore
|
||||
pullPolicyNotAlways: warning
|
||||
# healthChecks
|
||||
readinessProbeMissing: warning
|
||||
livenessProbeMissing: warning
|
||||
|
||||
@@ -3,95 +3,39 @@ module github.com/fairwindsops/polaris
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.60.0
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.7.0
|
||||
git.apache.org/thrift.git v0.12.0 // indirect
|
||||
github.com/Azure/go-autorest v12.4.3+incompatible
|
||||
github.com/Azure/go-autorest/autorest v0.10.0 // indirect
|
||||
github.com/appscode/jsonpatch v0.0.0-20190108182946-7c0e3b262f30
|
||||
github.com/beorn7/perks v1.0.1
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/evanphx/json-patch v4.5.0+incompatible
|
||||
github.com/go-logr/logr v0.1.0
|
||||
github.com/go-logr/zapr v0.1.1
|
||||
github.com/gobuffalo/depgen v0.1.0 // indirect
|
||||
github.com/gobuffalo/envy v1.9.0
|
||||
github.com/gobuffalo/genny v0.6.0
|
||||
github.com/gobuffalo/gogen v0.2.0
|
||||
github.com/gobuffalo/logger v1.0.3
|
||||
github.com/gobuffalo/mapi v1.2.1
|
||||
github.com/gobuffalo/packd v1.0.0
|
||||
cloud.google.com/go v0.65.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.4 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.2 // indirect
|
||||
github.com/fairwindsops/controller-utils v0.1.0
|
||||
github.com/gobuffalo/packr/v2 v2.8.0
|
||||
github.com/gobuffalo/syncx v0.1.0
|
||||
github.com/gogo/protobuf v1.3.1
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7 // indirect
|
||||
github.com/golang/protobuf v1.4.2
|
||||
github.com/google/btree v1.0.0
|
||||
github.com/google/gofuzz v1.1.0
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/googleapis/gnostic v0.3.1
|
||||
github.com/gophercloud/gophercloud v0.0.0-20190516165734-b3a23cc94cc5
|
||||
github.com/gorilla/mux v1.7.4
|
||||
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.14.6
|
||||
github.com/hashicorp/golang-lru v0.5.4
|
||||
github.com/imdario/mergo v0.3.9
|
||||
github.com/joho/godotenv v1.3.0
|
||||
github.com/json-iterator/go v1.1.10
|
||||
github.com/karrick/godirwalk v1.15.6
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3
|
||||
github.com/markbates/oncer v1.0.0
|
||||
github.com/markbates/safe v1.0.1
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
|
||||
github.com/modern-go/reflect2 v1.0.1
|
||||
github.com/pborman/uuid v0.0.0-20180906182336-adf5a7427709
|
||||
github.com/petar/GoLLRB v0.0.0-20190514000832-33fb24c13b99
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pmezard/go-difflib v1.0.0
|
||||
github.com/prometheus/client_golang v1.7.1
|
||||
github.com/prometheus/client_model v0.2.0
|
||||
github.com/prometheus/common v0.10.0
|
||||
github.com/prometheus/procfs v0.1.3
|
||||
github.com/google/go-cmp v0.5.2 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/gophercloud/gophercloud v0.12.0 // indirect
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/imdario/mergo v0.3.11 // indirect
|
||||
github.com/jessevdk/go-flags v1.4.0 // indirect
|
||||
github.com/karrick/godirwalk v1.16.1 // indirect
|
||||
github.com/kr/pretty v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.13.0 // indirect
|
||||
github.com/qri-io/jsonpointer v0.1.1 // indirect
|
||||
github.com/qri-io/jsonschema v0.1.1
|
||||
github.com/rogpeppe/go-internal v1.6.0
|
||||
github.com/sirupsen/logrus v1.6.0
|
||||
github.com/rogpeppe/go-internal v1.6.2 // indirect
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.6.1
|
||||
gitlab.com/golang-commonmark/html v0.0.0-20180917080848-cfaf75183c4a
|
||||
gitlab.com/golang-commonmark/linkify v0.0.0-20180917065525-c22b7bdb1179
|
||||
gitlab.com/golang-commonmark/markdown v0.0.0-20181102083822-772775880e1f
|
||||
gitlab.com/golang-commonmark/mdurl v0.0.0-20180912090424-e5bce34c34f2
|
||||
gitlab.com/golang-commonmark/puny v0.0.0-20180912090636-2cd490539afe
|
||||
go.opencensus.io v0.22.4
|
||||
go.uber.org/atomic v1.6.0
|
||||
go.uber.org/multierr v1.5.0
|
||||
go.uber.org/zap v1.15.0
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1
|
||||
golang.org/x/text v0.3.3
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
|
||||
golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f
|
||||
google.golang.org/api v0.28.0
|
||||
google.golang.org/appengine v1.6.6
|
||||
google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5
|
||||
google.golang.org/grpc v1.30.0
|
||||
gopkg.in/inf.v0 v0.9.1
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
|
||||
k8s.io/api v0.0.0-20181213150558-05914d821849
|
||||
k8s.io/apimachinery v0.0.0-20181127025237-2b1284ed4c93
|
||||
k8s.io/client-go v0.0.0-20181213151034-8d9ed539ba31
|
||||
k8s.io/klog v0.4.0
|
||||
k8s.io/kube-openapi v0.0.0-20190510232812-a01b7d5d6c22
|
||||
sigs.k8s.io/controller-runtime v0.1.10
|
||||
gitlab.com/golang-commonmark/linkify v0.0.0-20200225224916-64bca66f6ad3 // indirect
|
||||
gitlab.com/golang-commonmark/markdown v0.0.0-20191127184510-91b5b3c99c19
|
||||
go.uber.org/zap v1.16.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect
|
||||
golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 // indirect
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.1.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
|
||||
k8s.io/api v0.18.9
|
||||
k8s.io/apimachinery v0.18.9
|
||||
k8s.io/client-go v0.18.9
|
||||
k8s.io/klog/v2 v2.1.0 // indirect
|
||||
sigs.k8s.io/controller-runtime v0.6.3
|
||||
sigs.k8s.io/yaml v1.2.0
|
||||
)
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
|
||||
const (
|
||||
// Version represents the current release version of Polaris
|
||||
Version = "1.1.1"
|
||||
Version = "1.2.1"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -184,14 +184,14 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
|
||||
router.HandleFunc("/results.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
adjustedConf := getConfigForQuery(c, r.URL.Query())
|
||||
if auditData == nil {
|
||||
k, err := kube.CreateResourceProvider(auditPath)
|
||||
k, err := kube.CreateResourceProvider(r.Context(), auditPath, "")
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Kubernetes resources %v", err)
|
||||
http.Error(w, "Error fetching Kubernetes resources", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
auditDataObj, err := validator.RunAudit(adjustedConf, k)
|
||||
auditDataObj, err := validator.RunAudit(r.Context(), adjustedConf, k)
|
||||
if err != nil {
|
||||
http.Error(w, "Error Fetching Deployments", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -217,14 +217,14 @@ func GetRouter(c config.Configuration, auditPath string, port int, basePath stri
|
||||
adjustedConf := getConfigForQuery(c, r.URL.Query())
|
||||
|
||||
if auditData == nil {
|
||||
k, err := kube.CreateResourceProvider(auditPath)
|
||||
k, err := kube.CreateResourceProvider(r.Context(), auditPath, "")
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Kubernetes resources %v", err)
|
||||
http.Error(w, "Error fetching Kubernetes resources", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
auditData, err := validator.RunAudit(adjustedConf, k)
|
||||
auditData, err := validator.RunAudit(r.Context(), adjustedConf, k)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error getting audit data: %v", err)
|
||||
http.Error(w, "Error running audit", 500)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{define "footer"}}
|
||||
<div class="footer">
|
||||
<a href="https://fairwinds.com?source=polaris" target="_blank">©2019 FairwindsOps Inc.</a>
|
||||
<a href="https://fairwinds.com?source=polaris" target="_blank">©2020 FairwindsOps Inc.</a>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
+77
-59
@@ -2,6 +2,8 @@ package kube
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -11,7 +13,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -42,11 +43,75 @@ type k8sResource struct {
|
||||
var podSpecFields = []string{"jobTemplate", "spec", "template"}
|
||||
|
||||
// CreateResourceProvider returns a new ResourceProvider object to interact with k8s resources
|
||||
func CreateResourceProvider(directory string) (*ResourceProvider, error) {
|
||||
func CreateResourceProvider(ctx context.Context, directory, workload string) (*ResourceProvider, error) {
|
||||
if workload != "" {
|
||||
return CreateResourceProviderFromWorkload(ctx, workload)
|
||||
}
|
||||
if directory != "" {
|
||||
return CreateResourceProviderFromPath(directory)
|
||||
}
|
||||
return CreateResourceProviderFromCluster()
|
||||
return CreateResourceProviderFromCluster(ctx)
|
||||
}
|
||||
|
||||
// CreateResourceProviderFromWorkload creates a new ResourceProvider that just contains one workload
|
||||
func CreateResourceProviderFromWorkload(ctx context.Context, workload string) (*ResourceProvider, error) {
|
||||
kubeConf, configError := config.GetConfig()
|
||||
if configError != nil {
|
||||
logrus.Errorf("Error fetching KubeConfig: %v", configError)
|
||||
return nil, configError
|
||||
}
|
||||
kube, err := kubernetes.NewForConfig(kubeConf)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error creating Kubernetes client: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
serverVersion, err := kube.Discovery().ServerVersion()
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Cluster API version: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
resources := ResourceProvider{
|
||||
ServerVersion: serverVersion.Major + "." + serverVersion.Minor,
|
||||
SourceType: "Workload",
|
||||
SourceName: workload,
|
||||
CreationTime: time.Now(),
|
||||
Nodes: []corev1.Node{},
|
||||
Namespaces: []corev1.Namespace{},
|
||||
}
|
||||
|
||||
parts := strings.Split(workload, "/")
|
||||
if len(parts) != 4 {
|
||||
return nil, fmt.Errorf("Invalid workload identifier %s. Should be in format namespace/kind/version/name, e.g. nginx-ingress/Deployment.apps/v1/default-backend", workload)
|
||||
}
|
||||
namespace := parts[0]
|
||||
kind := parts[1]
|
||||
version := parts[2]
|
||||
name := parts[3]
|
||||
|
||||
dynamicInterface, err := dynamic.NewForConfig(kubeConf)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error connecting to dynamic interface: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
groupResources, err := restmapper.GetAPIGroupResources(kube.Discovery())
|
||||
if err != nil {
|
||||
logrus.Errorf("Error getting API Group resources: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
restMapper := restmapper.NewDiscoveryRESTMapper(groupResources)
|
||||
obj, err := getObject(ctx, namespace, kind, version, name, &dynamicInterface, &restMapper)
|
||||
if err != nil {
|
||||
logrus.Errorf("Could not find workload %s: %v", workload, err)
|
||||
return nil, err
|
||||
}
|
||||
workloadObj, err := NewGenericWorkloadFromUnstructured(kind, obj)
|
||||
if err != nil {
|
||||
logrus.Errorf("Could not parse workload %s: %v", workload, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resources.Controllers = []GenericWorkload{workloadObj}
|
||||
return &resources, nil
|
||||
}
|
||||
|
||||
// CreateResourceProviderFromPath returns a new ResourceProvider using the YAML files in a directory
|
||||
@@ -90,7 +155,7 @@ func CreateResourceProviderFromPath(directory string) (*ResourceProvider, error)
|
||||
}
|
||||
|
||||
// CreateResourceProviderFromCluster creates a new ResourceProvider using live data from a cluster
|
||||
func CreateResourceProviderFromCluster() (*ResourceProvider, error) {
|
||||
func CreateResourceProviderFromCluster(ctx context.Context) (*ResourceProvider, error) {
|
||||
kubeConf, configError := config.GetConfig()
|
||||
if configError != nil {
|
||||
logrus.Errorf("Error fetching KubeConfig: %v", configError)
|
||||
@@ -106,11 +171,11 @@ func CreateResourceProviderFromCluster() (*ResourceProvider, error) {
|
||||
logrus.Errorf("Error connecting to dynamic interface: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return CreateResourceProviderFromAPI(api, kubeConf.Host, &dynamicInterface)
|
||||
return CreateResourceProviderFromAPI(ctx, api, kubeConf.Host, &dynamicInterface)
|
||||
}
|
||||
|
||||
// CreateResourceProviderFromAPI creates a new ResourceProvider from an existing k8s interface
|
||||
func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string, dynamic *dynamic.Interface) (*ResourceProvider, error) {
|
||||
func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interface, clusterName string, dynamic *dynamic.Interface) (*ResourceProvider, error) {
|
||||
listOpts := metav1.ListOptions{}
|
||||
serverVersion, err := kube.Discovery().ServerVersion()
|
||||
if err != nil {
|
||||
@@ -118,17 +183,17 @@ func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nodes, err := kube.CoreV1().Nodes().List(listOpts)
|
||||
nodes, err := kube.CoreV1().Nodes().List(ctx, listOpts)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Nodes: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
namespaces, err := kube.CoreV1().Namespaces().List(listOpts)
|
||||
namespaces, err := kube.CoreV1().Namespaces().List(ctx, listOpts)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Namespaces: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
pods, err := kube.CoreV1().Pods("").List(listOpts)
|
||||
pods, err := kube.CoreV1().Pods("").List(ctx, listOpts)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error fetching Pods: %v", err)
|
||||
return nil, err
|
||||
@@ -143,7 +208,7 @@ func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string
|
||||
|
||||
objectCache := map[string]unstructured.Unstructured{}
|
||||
|
||||
controllers, err := LoadControllers(pods.Items, dynamic, &restMapper, objectCache)
|
||||
controllers, err := LoadControllers(ctx, pods.Items, dynamic, &restMapper, objectCache)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error loading controllers from pods: %v", err)
|
||||
return nil, err
|
||||
@@ -162,7 +227,7 @@ func CreateResourceProviderFromAPI(kube kubernetes.Interface, clusterName string
|
||||
}
|
||||
|
||||
// LoadControllers loads a list of controllers from the kubeResources Pods
|
||||
func LoadControllers(pods []corev1.Pod, dynamicClientPointer *dynamic.Interface, restMapperPointer *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) ([]GenericWorkload, error) {
|
||||
func LoadControllers(ctx context.Context, pods []corev1.Pod, dynamicClientPointer *dynamic.Interface, restMapperPointer *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) ([]GenericWorkload, error) {
|
||||
interfaces := []GenericWorkload{}
|
||||
deduped := map[string]corev1.Pod{}
|
||||
for _, pod := range pods {
|
||||
@@ -174,7 +239,7 @@ func LoadControllers(pods []corev1.Pod, dynamicClientPointer *dynamic.Interface,
|
||||
deduped[pod.ObjectMeta.Namespace+"/"+owners[0].Kind+"/"+owners[0].Name] = pod
|
||||
}
|
||||
for _, pod := range deduped {
|
||||
workload, err := NewGenericWorkload(pod, dynamicClientPointer, restMapperPointer, objectCache)
|
||||
workload, err := NewGenericWorkload(ctx, pod, dynamicClientPointer, restMapperPointer, objectCache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -201,19 +266,6 @@ func deduplicateControllers(inputControllers []GenericWorkload) []GenericWorkloa
|
||||
return results
|
||||
}
|
||||
|
||||
// GetPodSpec looks inside arbitrary YAML for a PodSpec
|
||||
func GetPodSpec(yaml map[string]interface{}) interface{} {
|
||||
for _, child := range podSpecFields {
|
||||
if childYaml, ok := yaml[child]; ok {
|
||||
return GetPodSpec(childYaml.(map[string]interface{}))
|
||||
}
|
||||
}
|
||||
if _, ok := yaml["containers"]; ok {
|
||||
return yaml
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addResourcesFromReader(reader io.Reader, resources *ResourceProvider) error {
|
||||
contents, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
@@ -241,40 +293,6 @@ func addResourcesFromYaml(contents string, resources *ResourceProvider) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetWorkloadFromBytes parses a GenericWorkload
|
||||
func GetWorkloadFromBytes(contentBytes []byte) (*GenericWorkload, error) {
|
||||
yamlNode := make(map[string]interface{})
|
||||
err := yaml.Unmarshal(contentBytes, &yamlNode)
|
||||
if err != nil {
|
||||
logrus.Errorf("Invalid YAML: %s", string(contentBytes))
|
||||
return nil, err
|
||||
}
|
||||
finalDoc := make(map[string]interface{})
|
||||
finalDoc["metadata"] = yamlNode["metadata"]
|
||||
finalDoc["apiVersion"] = "v1"
|
||||
finalDoc["kind"] = "Pod"
|
||||
podSpec := GetPodSpec(yamlNode)
|
||||
if podSpec == nil {
|
||||
return nil, nil
|
||||
}
|
||||
finalDoc["spec"] = podSpec
|
||||
marshaledYaml, err := yaml.Marshal(finalDoc)
|
||||
if err != nil {
|
||||
logrus.Errorf("Could not marshal yaml: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
decoder := k8sYaml.NewYAMLOrJSONDecoder(bytes.NewReader(marshaledYaml), 1000)
|
||||
pod := corev1.Pod{}
|
||||
err = decoder.Decode(&pod)
|
||||
newController, err := NewGenericWorkloadFromPod(pod, yamlNode)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newController.Kind = yamlNode["kind"].(string)
|
||||
return &newController, nil
|
||||
}
|
||||
|
||||
func addResourceFromString(contents string, resources *ResourceProvider) error {
|
||||
contentBytes := []byte(contents)
|
||||
decoder := k8sYaml.NewYAMLOrJSONDecoder(bytes.NewReader(contentBytes), 1000)
|
||||
|
||||
@@ -2,6 +2,7 @@ package kube
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -87,10 +88,10 @@ func TestAddResourcesFromReader(t *testing.T) {
|
||||
|
||||
func TestGetResourceFromAPI(t *testing.T) {
|
||||
k8s, dynamicInterface := test.SetupTestAPI()
|
||||
k8s = test.SetupAddControllers(k8s, "test")
|
||||
k8s = test.SetupAddControllers(context.Background(), k8s, "test")
|
||||
// TODO find a way to mock out the dynamic client
|
||||
// and create fake pods in order to find all of the controllers.
|
||||
resources, err := CreateResourceProviderFromAPI(k8s, "test", &dynamicInterface)
|
||||
resources, err := CreateResourceProviderFromAPI(context.Background(), k8s, "test", &dynamicInterface)
|
||||
assert.Equal(t, nil, err, "Error should be nil")
|
||||
|
||||
assert.Equal(t, "Cluster", resources.SourceType, "Should have type Path")
|
||||
|
||||
+108
-57
@@ -1,15 +1,20 @@
|
||||
package kube
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/fairwindsops/controller-utils/pkg/controller"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
kubeAPICoreV1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
kubeAPIMetaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
k8sYaml "k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/client-go/dynamic"
|
||||
)
|
||||
|
||||
@@ -21,12 +26,51 @@ type GenericWorkload struct {
|
||||
OriginalObjectJSON []byte
|
||||
}
|
||||
|
||||
// NewGenericWorkloadFromUnstructured creates a workload from an unstructured.Unstructured
|
||||
func NewGenericWorkloadFromUnstructured(kind string, unst *unstructured.Unstructured) (GenericWorkload, error) {
|
||||
workload := GenericWorkload{
|
||||
Kind: kind,
|
||||
}
|
||||
|
||||
objMeta, err := meta.Accessor(unst)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
workload.ObjectMeta = objMeta
|
||||
|
||||
b, err := json.Marshal(unst)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
workload.OriginalObjectJSON = b
|
||||
|
||||
m := make(map[string]interface{})
|
||||
err = json.Unmarshal(b, &m)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
podSpecMap := controller.GetPodSpec(m)
|
||||
b, err = json.Marshal(podSpecMap)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
podSpecObject := kubeAPICoreV1.PodSpec{}
|
||||
err = json.Unmarshal(b, &podSpecObject)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
workload.PodSpec = podSpecObject
|
||||
|
||||
return workload, nil
|
||||
}
|
||||
|
||||
// NewGenericWorkloadFromPod builds a new workload for a given Pod without looking at parents
|
||||
func NewGenericWorkloadFromPod(podResource kubeAPICoreV1.Pod, originalObject interface{}) (GenericWorkload, error) {
|
||||
workload := GenericWorkload{}
|
||||
workload.PodSpec = podResource.Spec
|
||||
workload.ObjectMeta = podResource.ObjectMeta.GetObjectMeta()
|
||||
workload.Kind = "Pod"
|
||||
workload := GenericWorkload{
|
||||
Kind: "Pod",
|
||||
PodSpec: podResource.Spec,
|
||||
ObjectMeta: podResource.ObjectMeta.GetObjectMeta(),
|
||||
}
|
||||
if originalObject != nil {
|
||||
bytes, err := json.Marshal(originalObject)
|
||||
if err != nil {
|
||||
@@ -38,8 +82,8 @@ func NewGenericWorkloadFromPod(podResource kubeAPICoreV1.Pod, originalObject int
|
||||
}
|
||||
|
||||
// NewGenericWorkload builds a new workload for a given Pod
|
||||
func NewGenericWorkload(podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericWorkload, error) {
|
||||
workload, err := newGenericWorkload(podResource, dynamicClient, restMapper, objectCache)
|
||||
func NewGenericWorkload(ctx context.Context, podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericWorkload, error) {
|
||||
workload, err := newGenericWorkload(ctx, podResource, dynamicClient, restMapper, objectCache)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
@@ -49,66 +93,29 @@ func NewGenericWorkload(podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.In
|
||||
return workload, err
|
||||
}
|
||||
|
||||
func newGenericWorkload(podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericWorkload, error) {
|
||||
func newGenericWorkload(ctx context.Context, podResource kubeAPICoreV1.Pod, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) (GenericWorkload, error) {
|
||||
workload, err := NewGenericWorkloadFromPod(podResource, nil)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
// If an owner exists then set the name to the workload.
|
||||
// This allows us to handle CRDs creating Workloads or DeploymentConfigs in OpenShift.
|
||||
owners := workload.ObjectMeta.GetOwnerReferences()
|
||||
lastKey := ""
|
||||
for len(owners) > 0 {
|
||||
if len(owners) > 1 {
|
||||
logrus.Warn("More than 1 owner found")
|
||||
}
|
||||
firstOwner := owners[0]
|
||||
if firstOwner.Kind == "Node" {
|
||||
break
|
||||
}
|
||||
workload.Kind = firstOwner.Kind
|
||||
key := fmt.Sprintf("%s/%s/%s", firstOwner.Kind, workload.ObjectMeta.GetNamespace(), firstOwner.Name)
|
||||
lastKey = key
|
||||
abstractObject, ok := objectCache[key]
|
||||
if !ok {
|
||||
err = cacheAllObjectsOfKind(firstOwner.APIVersion, firstOwner.Kind, dynamicClient, restMapper, objectCache)
|
||||
if err != nil {
|
||||
logrus.Warnf("Error caching objects of Kind %s %v", firstOwner.Kind, err)
|
||||
return workload, nil // Note -we don't return an error so we can recover from the case where RBAC is insufficient
|
||||
}
|
||||
abstractObject, ok = objectCache[key]
|
||||
if !ok {
|
||||
logrus.Errorf("Cache missed %s again", key)
|
||||
return workload, nil
|
||||
}
|
||||
}
|
||||
|
||||
objMeta, err := meta.Accessor(&abstractObject)
|
||||
if err != nil {
|
||||
logrus.Warnf("Error retrieving parent metadata %s of API %s and Kind %s because of error: %v ", firstOwner.Name, firstOwner.APIVersion, firstOwner.Kind, err)
|
||||
return workload, err
|
||||
}
|
||||
workload.ObjectMeta = objMeta
|
||||
owners = abstractObject.GetOwnerReferences()
|
||||
objMeta, err := meta.Accessor(&podResource)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
controllerObject, err := controller.GetTopController(ctx, *dynamicClient, *restMapper, objMeta)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
|
||||
if lastKey != "" {
|
||||
bytes, err := json.Marshal(objectCache[lastKey])
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
workload.OriginalObjectJSON = bytes
|
||||
} else {
|
||||
bytes, err := json.Marshal(podResource)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
workload.OriginalObjectJSON = bytes
|
||||
bytes, err := json.Marshal(controllerObject)
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
workload.OriginalObjectJSON = bytes
|
||||
return workload, nil
|
||||
}
|
||||
|
||||
func cacheAllObjectsOfKind(apiVersion, kind string, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) error {
|
||||
func cacheAllObjectsOfKind(ctx context.Context, apiVersion, kind string, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper, objectCache map[string]unstructured.Unstructured) error {
|
||||
fqKind := schema.FromAPIVersionAndKind(apiVersion, kind)
|
||||
mapping, err := (*restMapper).RESTMapping(fqKind.GroupKind(), fqKind.Version)
|
||||
if err != nil {
|
||||
@@ -116,7 +123,7 @@ func cacheAllObjectsOfKind(apiVersion, kind string, dynamicClient *dynamic.Inter
|
||||
return err
|
||||
}
|
||||
|
||||
objects, err := (*dynamicClient).Resource(mapping.Resource).Namespace("").List(kubeAPIMetaV1.ListOptions{})
|
||||
objects, err := (*dynamicClient).Resource(mapping.Resource).Namespace("").List(ctx, kubeAPIMetaV1.ListOptions{})
|
||||
if err != nil {
|
||||
logrus.Warnf("Error retrieving parent object API %s and Kind %s because of error: %v ", mapping.Resource.Version, mapping.Resource.Resource, err)
|
||||
return err
|
||||
@@ -127,3 +134,47 @@ func cacheAllObjectsOfKind(apiVersion, kind string, dynamicClient *dynamic.Inter
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getObject(ctx context.Context, namespace, kind, version, name string, dynamicClient *dynamic.Interface, restMapper *meta.RESTMapper) (*unstructured.Unstructured, error) {
|
||||
fqKind := schema.ParseGroupKind(kind)
|
||||
mapping, err := (*restMapper).RESTMapping(fqKind, version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
object, err := (*dynamicClient).Resource(mapping.Resource).Namespace(namespace).Get(ctx, name, kubeAPIMetaV1.GetOptions{})
|
||||
return object, err
|
||||
}
|
||||
|
||||
// GetWorkloadFromBytes parses a GenericWorkload
|
||||
func GetWorkloadFromBytes(contentBytes []byte) (*GenericWorkload, error) {
|
||||
yamlNode := make(map[string]interface{})
|
||||
err := yaml.Unmarshal(contentBytes, &yamlNode)
|
||||
if err != nil {
|
||||
logrus.Errorf("Invalid YAML: %s", string(contentBytes))
|
||||
return nil, err
|
||||
}
|
||||
finalDoc := make(map[string]interface{})
|
||||
finalDoc["metadata"] = yamlNode["metadata"]
|
||||
finalDoc["apiVersion"] = "v1"
|
||||
finalDoc["kind"] = "Pod"
|
||||
podSpecObject := podspec.GetPodSpec(yamlNode)
|
||||
if podSpecObject == nil {
|
||||
return nil, nil
|
||||
}
|
||||
finalDoc["spec"] = podSpecObject
|
||||
marshaledYaml, err := yaml.Marshal(finalDoc)
|
||||
if err != nil {
|
||||
logrus.Errorf("Could not marshal yaml: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
decoder := k8sYaml.NewYAMLOrJSONDecoder(bytes.NewReader(marshaledYaml), 1000)
|
||||
pod := kubeAPICoreV1.Pod{}
|
||||
err = decoder.Decode(&pod)
|
||||
newController, err := NewGenericWorkloadFromPod(pod, yamlNode)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newController.Kind = yamlNode["kind"].(string)
|
||||
return &newController, nil
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
|
||||
@@ -22,8 +24,8 @@ import (
|
||||
)
|
||||
|
||||
// ValidateContainer validates a single container from a given controller
|
||||
func ValidateContainer(conf *config.Configuration, controller kube.GenericWorkload, container *corev1.Container, isInit bool) (ContainerResult, error) {
|
||||
results, err := applyContainerSchemaChecks(conf, controller, container, isInit)
|
||||
func ValidateContainer(ctx context.Context, conf *config.Configuration, controller kube.GenericWorkload, container *corev1.Container, isInit bool) (ContainerResult, error) {
|
||||
results, err := applyContainerSchemaChecks(ctx, conf, controller, container, isInit)
|
||||
if err != nil {
|
||||
return ContainerResult{}, err
|
||||
}
|
||||
@@ -37,18 +39,18 @@ func ValidateContainer(conf *config.Configuration, controller kube.GenericWorklo
|
||||
}
|
||||
|
||||
// ValidateAllContainers validates both init and regular containers
|
||||
func ValidateAllContainers(conf *config.Configuration, controller kube.GenericWorkload) ([]ContainerResult, error) {
|
||||
func ValidateAllContainers(ctx context.Context, conf *config.Configuration, controller kube.GenericWorkload) ([]ContainerResult, error) {
|
||||
results := []ContainerResult{}
|
||||
pod := controller.PodSpec
|
||||
for _, container := range pod.InitContainers {
|
||||
result, err := ValidateContainer(conf, controller, &container, true)
|
||||
result, err := ValidateContainer(ctx, conf, controller, &container, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
for _, container := range pod.Containers {
|
||||
result, err := ValidateContainer(conf, controller, &container, false)
|
||||
result, err := ValidateContainer(ctx, conf, controller, &container, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
@@ -68,7 +69,7 @@ func testValidateWithWorkload(t *testing.T, container *corev1.Container, resourc
|
||||
parsedConf, err := conf.Parse([]byte(*resourceConf))
|
||||
assert.NoError(t, err, "Expected no error when parsing config")
|
||||
|
||||
results, err := applyContainerSchemaChecks(&parsedConf, workload, container, false)
|
||||
results, err := applyContainerSchemaChecks(context.Background(), &parsedConf, workload, container, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -89,7 +90,7 @@ func TestValidateResourcesEmptyConfig(t *testing.T) {
|
||||
Name: "Empty",
|
||||
}
|
||||
|
||||
results, err := applyContainerSchemaChecks(&conf.Configuration{}, getEmptyWorkload(t, ""), container, false)
|
||||
results, err := applyContainerSchemaChecks(context.Background(), &conf.Configuration{}, getEmptyWorkload(t, ""), container, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -186,7 +187,7 @@ func TestValidateHealthChecks(t *testing.T) {
|
||||
for idx, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
controller := getEmptyWorkload(t, "")
|
||||
results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.probes}, controller, tt.container, tt.isInit)
|
||||
results, err := applyContainerSchemaChecks(context.Background(), &conf.Configuration{Checks: tt.probes}, controller, tt.container, tt.isInit)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -300,7 +301,7 @@ func TestValidateImage(t *testing.T) {
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
controller := getEmptyWorkload(t, "")
|
||||
results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.image}, controller, tt.container, false)
|
||||
results, err := applyContainerSchemaChecks(context.Background(), &conf.Configuration{Checks: tt.image}, controller, tt.container, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -417,7 +418,7 @@ func TestValidateNetworking(t *testing.T) {
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
controller := getEmptyWorkload(t, "")
|
||||
results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.networkConf}, controller, tt.container, false)
|
||||
results, err := applyContainerSchemaChecks(context.Background(), &conf.Configuration{Checks: tt.networkConf}, controller, tt.container, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -922,7 +923,7 @@ func TestValidateSecurity(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
workload, err := kube.NewGenericWorkloadFromPod(corev1.Pod{Spec: *tt.pod}, nil)
|
||||
assert.NoError(t, err)
|
||||
results, err := applyContainerSchemaChecks(&conf.Configuration{Checks: tt.securityConf}, workload, tt.container, false)
|
||||
results, err := applyContainerSchemaChecks(context.Background(), &conf.Configuration{Checks: tt.securityConf}, workload, tt.container, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -1067,7 +1068,7 @@ func TestValidateRunAsRoot(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
workload, err := kube.NewGenericWorkloadFromPod(corev1.Pod{Spec: *tt.pod}, nil)
|
||||
assert.NoError(t, err)
|
||||
results, err := applyContainerSchemaChecks(&config, workload, tt.container, false)
|
||||
results, err := applyContainerSchemaChecks(context.Background(), &config, workload, tt.container, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -26,13 +27,13 @@ import (
|
||||
const exemptionAnnotationKey = "polaris.fairwinds.com/exempt"
|
||||
|
||||
// ValidateController validates a single controller, returns a ControllerResult.
|
||||
func ValidateController(conf *conf.Configuration, controller kube.GenericWorkload) (ControllerResult, error) {
|
||||
podResult, err := ValidatePod(conf, controller)
|
||||
func ValidateController(ctx context.Context, conf *conf.Configuration, controller kube.GenericWorkload) (ControllerResult, error) {
|
||||
podResult, err := ValidatePod(ctx, conf, controller)
|
||||
if err != nil {
|
||||
return ControllerResult{}, err
|
||||
}
|
||||
|
||||
controllerResult, err := applyControllerSchemaChecks(conf, controller)
|
||||
controllerResult, err := applyControllerSchemaChecks(ctx, conf, controller)
|
||||
if err != nil {
|
||||
return ControllerResult{}, err
|
||||
}
|
||||
@@ -50,7 +51,7 @@ func ValidateController(conf *conf.Configuration, controller kube.GenericWorkloa
|
||||
|
||||
// ValidateControllers validates that each deployment conforms to the Polaris config,
|
||||
// builds a list of ResourceResults organized by namespace.
|
||||
func ValidateControllers(config *conf.Configuration, kubeResources *kube.ResourceProvider) ([]ControllerResult, error) {
|
||||
func ValidateControllers(ctx context.Context, config *conf.Configuration, kubeResources *kube.ResourceProvider) ([]ControllerResult, error) {
|
||||
controllersToAudit := kubeResources.Controllers
|
||||
|
||||
results := []ControllerResult{}
|
||||
@@ -58,7 +59,7 @@ func ValidateControllers(config *conf.Configuration, kubeResources *kube.Resourc
|
||||
if !config.DisallowExemptions && hasExemptionAnnotation(controller) {
|
||||
continue
|
||||
}
|
||||
result, err := ValidateController(config, controller)
|
||||
result, err := ValidateController(ctx, config, controller)
|
||||
if err != nil {
|
||||
logrus.Warn("An error occured validating controller:", err)
|
||||
return nil, err
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -38,7 +39,7 @@ func TestValidateController(t *testing.T) {
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(2),
|
||||
Warnings: uint(0),
|
||||
Dangers: uint(0),
|
||||
Dangers: uint(0),
|
||||
}
|
||||
|
||||
expectedResults := ResultSet{
|
||||
@@ -46,7 +47,7 @@ func TestValidateController(t *testing.T) {
|
||||
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
|
||||
}
|
||||
|
||||
actualResult, err := ValidateController(&c, deployment)
|
||||
actualResult, err := ValidateController(context.Background(), &c, deployment)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -72,7 +73,7 @@ func TestControllerLevelChecks(t *testing.T) {
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(0),
|
||||
Warnings: uint(0),
|
||||
Dangers: uint(1),
|
||||
Dangers: uint(1),
|
||||
}
|
||||
|
||||
expectedResults := ResultSet{
|
||||
@@ -81,7 +82,7 @@ func TestControllerLevelChecks(t *testing.T) {
|
||||
|
||||
for _, controller := range resources.Controllers {
|
||||
if controller.Kind == "Deployment" && controller.ObjectMeta.GetName() == "test-deployment" {
|
||||
actualResult, err := ValidateController(&c, controller)
|
||||
actualResult, err := ValidateController(context.Background(), &c, controller)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -110,13 +111,13 @@ func TestSkipHealthChecks(t *testing.T) {
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(0),
|
||||
Warnings: uint(1),
|
||||
Dangers: uint(1),
|
||||
Dangers: uint(1),
|
||||
}
|
||||
expectedResults := ResultSet{
|
||||
"readinessProbeMissing": {ID: "readinessProbeMissing", Message: "Readiness probe should be configured", Success: false, Severity: "danger", Category: "Health Checks"},
|
||||
"livenessProbeMissing": {ID: "livenessProbeMissing", Message: "Liveness probe should be configured", Success: false, Severity: "warning", Category: "Health Checks"},
|
||||
}
|
||||
actualResult, err := ValidateController(&c, deployment)
|
||||
actualResult, err := ValidateController(context.Background(), &c, deployment)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -132,10 +133,10 @@ func TestSkipHealthChecks(t *testing.T) {
|
||||
expectedSum = CountSummary{
|
||||
Successes: uint(0),
|
||||
Warnings: uint(0),
|
||||
Dangers: uint(0),
|
||||
Dangers: uint(0),
|
||||
}
|
||||
expectedResults = ResultSet{}
|
||||
actualResult, err = ValidateController(&c, job)
|
||||
actualResult, err = ValidateController(context.Background(), &c, job)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -150,10 +151,10 @@ func TestSkipHealthChecks(t *testing.T) {
|
||||
expectedSum = CountSummary{
|
||||
Successes: uint(0),
|
||||
Warnings: uint(0),
|
||||
Dangers: uint(0),
|
||||
Dangers: uint(0),
|
||||
}
|
||||
expectedResults = ResultSet{}
|
||||
actualResult, err = ValidateController(&c, cronjob)
|
||||
actualResult, err = ValidateController(context.Background(), &c, cronjob)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -181,9 +182,9 @@ func TestControllerExemptions(t *testing.T) {
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(0),
|
||||
Warnings: uint(1),
|
||||
Dangers: uint(1),
|
||||
Dangers: uint(1),
|
||||
}
|
||||
actualResults, err := ValidateControllers(&c, resources)
|
||||
actualResults, err := ValidateControllers(context.Background(), &c, resources)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -194,7 +195,7 @@ func TestControllerExemptions(t *testing.T) {
|
||||
resources.Controllers[0].ObjectMeta.SetAnnotations(map[string]string{
|
||||
exemptionAnnotationKey: "true",
|
||||
})
|
||||
actualResults, err = ValidateControllers(&c, resources)
|
||||
actualResults, err = ValidateControllers(context.Background(), &c, resources)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package validator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -16,13 +17,13 @@ import (
|
||||
)
|
||||
|
||||
// RunAudit runs a full Polaris audit and returns an AuditData object
|
||||
func RunAudit(config conf.Configuration, kubeResources *kube.ResourceProvider) (AuditData, error) {
|
||||
func RunAudit(ctx context.Context, config conf.Configuration, kubeResources *kube.ResourceProvider) (AuditData, error) {
|
||||
displayName := config.DisplayName
|
||||
if displayName == "" {
|
||||
displayName = kubeResources.SourceName
|
||||
}
|
||||
|
||||
results, err := ValidateControllers(&config, kubeResources)
|
||||
results, err := ValidateControllers(ctx, &config, kubeResources)
|
||||
if err != nil {
|
||||
return AuditData{}, err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
conf "github.com/fairwindsops/polaris/pkg/config"
|
||||
@@ -11,11 +12,11 @@ import (
|
||||
|
||||
func TestGetTemplateData(t *testing.T) {
|
||||
k8s, dynamicClient := test.SetupTestAPI()
|
||||
k8s = test.SetupAddControllers(k8s, "test")
|
||||
k8s = test.SetupAddExtraControllerVersions(k8s, "test-extra")
|
||||
k8s = test.SetupAddControllers(context.Background(), k8s, "test")
|
||||
k8s = test.SetupAddExtraControllerVersions(context.Background(), k8s, "test-extra")
|
||||
// TODO figure out how to mock out dynamic client.
|
||||
// and add in pods for all controllers to fill out tests.
|
||||
resources, err := kube.CreateResourceProviderFromAPI(k8s, "test", &dynamicClient)
|
||||
resources, err := kube.CreateResourceProviderFromAPI(context.Background(), k8s, "test", &dynamicClient)
|
||||
assert.Equal(t, err, nil, "error should be nil")
|
||||
|
||||
c := conf.Configuration{
|
||||
@@ -28,10 +29,11 @@ func TestGetTemplateData(t *testing.T) {
|
||||
sum := CountSummary{
|
||||
Successes: uint(0),
|
||||
Warnings: uint(1),
|
||||
Dangers: uint(1),
|
||||
Dangers: uint(1),
|
||||
}
|
||||
|
||||
actualAudit, err := RunAudit(c, resources)
|
||||
actualAudit, err := RunAudit(context.Background(), c, resources)
|
||||
|
||||
assert.Equal(t, err, nil, "error should be nil")
|
||||
|
||||
assert.EqualValues(t, sum, actualAudit.GetSummary())
|
||||
|
||||
@@ -15,13 +15,15 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
)
|
||||
|
||||
// ValidatePod validates that each pod conforms to the Polaris config, returns a ResourceResult.
|
||||
func ValidatePod(conf *config.Configuration, controller kube.GenericWorkload) (PodResult, error) {
|
||||
podResults, err := applyPodSchemaChecks(conf, controller)
|
||||
func ValidatePod(ctx context.Context, conf *config.Configuration, controller kube.GenericWorkload) (PodResult, error) {
|
||||
podResults, err := applyPodSchemaChecks(ctx, conf, controller)
|
||||
if err != nil {
|
||||
return PodResult{}, err
|
||||
}
|
||||
@@ -30,7 +32,7 @@ func ValidatePod(conf *config.Configuration, controller kube.GenericWorkload) (P
|
||||
ContainerResults: []ContainerResult{},
|
||||
}
|
||||
|
||||
pRes.ContainerResults, err = ValidateAllContainers(conf, controller)
|
||||
pRes.ContainerResults, err = ValidateAllContainers(ctx, conf, controller)
|
||||
if err != nil {
|
||||
return pRes, err
|
||||
}
|
||||
|
||||
+16
-15
@@ -15,6 +15,7 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -36,14 +37,14 @@ func TestValidatePod(t *testing.T) {
|
||||
}
|
||||
|
||||
k8s, _ := test.SetupTestAPI()
|
||||
k8s = test.SetupAddControllers(k8s, "test")
|
||||
k8s = test.SetupAddControllers(context.Background(), k8s, "test")
|
||||
p := test.MockPod()
|
||||
deployment, err := kube.NewGenericWorkloadFromPod(p, nil)
|
||||
assert.NoError(t, err)
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(4),
|
||||
Warnings: uint(0),
|
||||
Dangers: uint(0),
|
||||
Dangers: uint(0),
|
||||
}
|
||||
|
||||
expectedResults := ResultSet{
|
||||
@@ -52,7 +53,7 @@ func TestValidatePod(t *testing.T) {
|
||||
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
|
||||
}
|
||||
|
||||
actualPodResult, err := ValidatePod(&c, deployment)
|
||||
actualPodResult, err := ValidatePod(context.Background(), &c, deployment)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -73,7 +74,7 @@ func TestInvalidIPCPod(t *testing.T) {
|
||||
}
|
||||
|
||||
k8s, _ := test.SetupTestAPI()
|
||||
k8s = test.SetupAddControllers(k8s, "test")
|
||||
k8s = test.SetupAddControllers(context.Background(), k8s, "test")
|
||||
p := test.MockPod()
|
||||
p.Spec.HostIPC = true
|
||||
workload, err := kube.NewGenericWorkloadFromPod(p, nil)
|
||||
@@ -81,7 +82,7 @@ func TestInvalidIPCPod(t *testing.T) {
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(3),
|
||||
Warnings: uint(0),
|
||||
Dangers: uint(1),
|
||||
Dangers: uint(1),
|
||||
}
|
||||
expectedResults := ResultSet{
|
||||
"hostIPCSet": {ID: "hostIPCSet", Message: "Host IPC should not be configured", Success: false, Severity: "danger", Category: "Security"},
|
||||
@@ -89,7 +90,7 @@ func TestInvalidIPCPod(t *testing.T) {
|
||||
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
|
||||
}
|
||||
|
||||
actualPodResult, err := ValidatePod(&c, workload)
|
||||
actualPodResult, err := ValidatePod(context.Background(), &c, workload)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -110,7 +111,7 @@ func TestInvalidNeworkPod(t *testing.T) {
|
||||
}
|
||||
|
||||
k8s, _ := test.SetupTestAPI()
|
||||
k8s = test.SetupAddControllers(k8s, "test")
|
||||
k8s = test.SetupAddControllers(context.Background(), k8s, "test")
|
||||
p := test.MockPod()
|
||||
p.Spec.HostNetwork = true
|
||||
workload, err := kube.NewGenericWorkloadFromPod(p, nil)
|
||||
@@ -118,7 +119,7 @@ func TestInvalidNeworkPod(t *testing.T) {
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(3),
|
||||
Warnings: uint(1),
|
||||
Dangers: uint(0),
|
||||
Dangers: uint(0),
|
||||
}
|
||||
|
||||
expectedResults := ResultSet{
|
||||
@@ -127,7 +128,7 @@ func TestInvalidNeworkPod(t *testing.T) {
|
||||
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
|
||||
}
|
||||
|
||||
actualPodResult, err := ValidatePod(&c, workload)
|
||||
actualPodResult, err := ValidatePod(context.Background(), &c, workload)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -148,7 +149,7 @@ func TestInvalidPIDPod(t *testing.T) {
|
||||
}
|
||||
|
||||
k8s, _ := test.SetupTestAPI()
|
||||
k8s = test.SetupAddControllers(k8s, "test")
|
||||
k8s = test.SetupAddControllers(context.Background(), k8s, "test")
|
||||
p := test.MockPod()
|
||||
p.Spec.HostPID = true
|
||||
workload, err := kube.NewGenericWorkloadFromPod(p, nil)
|
||||
@@ -156,7 +157,7 @@ func TestInvalidPIDPod(t *testing.T) {
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(3),
|
||||
Warnings: uint(0),
|
||||
Dangers: uint(1),
|
||||
Dangers: uint(1),
|
||||
}
|
||||
|
||||
expectedResults := ResultSet{
|
||||
@@ -165,7 +166,7 @@ func TestInvalidPIDPod(t *testing.T) {
|
||||
"hostNetworkSet": {ID: "hostNetworkSet", Message: "Host network is not configured", Success: true, Severity: "warning", Category: "Networking"},
|
||||
}
|
||||
|
||||
actualPodResult, err := ValidatePod(&c, workload)
|
||||
actualPodResult, err := ValidatePod(context.Background(), &c, workload)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -192,7 +193,7 @@ func TestExemption(t *testing.T) {
|
||||
}
|
||||
|
||||
k8s, _ := test.SetupTestAPI()
|
||||
k8s = test.SetupAddControllers(k8s, "test")
|
||||
k8s = test.SetupAddControllers(context.Background(), k8s, "test")
|
||||
p := test.MockPod()
|
||||
p.Spec.HostIPC = true
|
||||
p.ObjectMeta = metav1.ObjectMeta{
|
||||
@@ -203,14 +204,14 @@ func TestExemption(t *testing.T) {
|
||||
expectedSum := CountSummary{
|
||||
Successes: uint(3),
|
||||
Warnings: uint(0),
|
||||
Dangers: uint(0),
|
||||
Dangers: uint(0),
|
||||
}
|
||||
expectedResults := ResultSet{
|
||||
"hostNetworkSet": {ID: "hostNetworkSet", Message: "Host network is not configured", Success: true, Severity: "warning", Category: "Networking"},
|
||||
"hostPIDSet": {ID: "hostPIDSet", Message: "Host PID is not configured", Success: true, Severity: "danger", Category: "Security"},
|
||||
}
|
||||
|
||||
actualPodResult, err := ValidatePod(&c, workload)
|
||||
actualPodResult, err := ValidatePod(context.Background(), &c, workload)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package validator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
@@ -113,7 +114,7 @@ func getExemptKey(checkID string) string {
|
||||
return fmt.Sprintf("polaris.fairwinds.com/%s-exempt", checkID)
|
||||
}
|
||||
|
||||
func applyPodSchemaChecks(conf *config.Configuration, controller kube.GenericWorkload) (ResultSet, error) {
|
||||
func applyPodSchemaChecks(ctx context.Context, conf *config.Configuration, controller kube.GenericWorkload) (ResultSet, error) {
|
||||
results := ResultSet{}
|
||||
checkIDs := getSortedKeys(conf.Checks)
|
||||
objectAnnotations := controller.ObjectMeta.GetAnnotations()
|
||||
@@ -138,7 +139,7 @@ func applyPodSchemaChecks(conf *config.Configuration, controller kube.GenericWor
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func applyControllerSchemaChecks(conf *config.Configuration, controller kube.GenericWorkload) (ResultSet, error) {
|
||||
func applyControllerSchemaChecks(ctx context.Context, conf *config.Configuration, controller kube.GenericWorkload) (ResultSet, error) {
|
||||
results := ResultSet{}
|
||||
checkIDs := getSortedKeys(conf.Checks)
|
||||
objectAnnotations := controller.ObjectMeta.GetAnnotations()
|
||||
@@ -163,7 +164,7 @@ func applyControllerSchemaChecks(conf *config.Configuration, controller kube.Gen
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func applyContainerSchemaChecks(conf *config.Configuration, controller kube.GenericWorkload, container *corev1.Container, isInit bool) (ResultSet, error) {
|
||||
func applyContainerSchemaChecks(ctx context.Context, conf *config.Configuration, controller kube.GenericWorkload, container *corev1.Container, isInit bool) (ResultSet, error) {
|
||||
results := ResultSet{}
|
||||
checkIDs := getSortedKeys(conf.Checks)
|
||||
objectAnnotations := controller.ObjectMeta.GetAnnotations()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
conf "github.com/fairwindsops/polaris/pkg/config"
|
||||
@@ -143,14 +144,14 @@ func TestValidateResourcesInit(t *testing.T) {
|
||||
parsedConf, err := conf.Parse([]byte(resourceConfRanges))
|
||||
assert.NoError(t, err, "Expected no error when parsing config")
|
||||
|
||||
results, err := applyContainerSchemaChecks(&parsedConf, controller, emptyContainer, false)
|
||||
results, err := applyContainerSchemaChecks(context.Background(), &parsedConf, controller, emptyContainer, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
assert.Equal(t, uint(1), results.GetSummary().Dangers)
|
||||
assert.Equal(t, uint(1), results.GetSummary().Warnings)
|
||||
|
||||
results, err = applyContainerSchemaChecks(&parsedConf, controller, emptyContainer, true)
|
||||
results, err = applyContainerSchemaChecks(context.Background(), &parsedConf, controller, emptyContainer, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
+42
-56
@@ -25,37 +25,25 @@ import (
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
validator "github.com/fairwindsops/polaris/pkg/validator"
|
||||
|
||||
"github.com/fairwindsops/controller-utils/pkg/podspec"
|
||||
"github.com/sirupsen/logrus"
|
||||
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/runtime/inject"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission/types"
|
||||
)
|
||||
|
||||
// Validator validates k8s resources.
|
||||
type Validator struct {
|
||||
client client.Client
|
||||
decoder types.Decoder
|
||||
Client client.Client
|
||||
decoder *admission.Decoder
|
||||
Config config.Configuration
|
||||
}
|
||||
|
||||
var _ inject.Client = &Validator{}
|
||||
|
||||
// InjectClient injects the client.
|
||||
func (v *Validator) InjectClient(c client.Client) error {
|
||||
v.client = c
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ inject.Decoder = &Validator{}
|
||||
|
||||
// InjectDecoder injects the decoder.
|
||||
func (v *Validator) InjectDecoder(d types.Decoder) error {
|
||||
func (v *Validator) InjectDecoder(d *admission.Decoder) error {
|
||||
logrus.Info("Injecting decoder")
|
||||
v.decoder = d
|
||||
return nil
|
||||
}
|
||||
@@ -63,28 +51,42 @@ func (v *Validator) InjectDecoder(d types.Decoder) error {
|
||||
var _ admission.Handler = &Validator{}
|
||||
|
||||
// NewWebhook creates a validating admission webhook for the apiType.
|
||||
func NewWebhook(name string, mgr manager.Manager, validator Validator, apiType runtime.Object) (*admission.Webhook, error) {
|
||||
name = fmt.Sprintf("%s.k8s.io", name)
|
||||
path := fmt.Sprintf("/validating-%s", name)
|
||||
func NewWebhook(mgr manager.Manager, validator Validator) {
|
||||
path := "/validate"
|
||||
|
||||
webhook, err := builder.NewWebhookBuilder().
|
||||
Name(name).
|
||||
Validating().
|
||||
Path(path).
|
||||
Operations(admissionregistrationv1beta1.Create, admissionregistrationv1beta1.Update).
|
||||
WithManager(mgr).
|
||||
ForType(apiType).
|
||||
Handlers(&validator).
|
||||
Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return webhook, nil
|
||||
mgr.GetWebhookServer().Register(path, &webhook.Admission{Handler: &validator})
|
||||
}
|
||||
|
||||
func (v *Validator) handleInternal(ctx context.Context, req types.Request) (*validator.PodResult, error) {
|
||||
// GetObjectFromRawRequest returns the pod object and the controller's object from the raw json bytes.
|
||||
func GetObjectFromRawRequest(raw []byte) (corev1.Pod, interface{}, error) {
|
||||
pod := corev1.Pod{}
|
||||
var originalObject interface{}
|
||||
|
||||
decoded := map[string]interface{}{}
|
||||
err := json.Unmarshal(raw, &decoded)
|
||||
if err != nil {
|
||||
return pod, originalObject, err
|
||||
}
|
||||
podMap := podspec.GetPodSpec(decoded)
|
||||
if podMap == nil {
|
||||
return pod, originalObject, errors.New("Object does not contain pods")
|
||||
}
|
||||
encoded, err := json.Marshal(podMap)
|
||||
if err != nil {
|
||||
return pod, originalObject, err
|
||||
}
|
||||
err = json.Unmarshal(encoded, &pod.Spec)
|
||||
if err != nil {
|
||||
return pod, originalObject, err
|
||||
}
|
||||
originalObject = decoded
|
||||
return pod, originalObject, err
|
||||
}
|
||||
|
||||
func (v *Validator) handleInternal(ctx context.Context, req admission.Request) (*validator.PodResult, error) {
|
||||
pod := corev1.Pod{}
|
||||
var originalObject interface{}
|
||||
var err error
|
||||
if req.AdmissionRequest.Kind.Kind == "Pod" {
|
||||
err := v.decoder.Decode(req, &pod)
|
||||
if err != nil {
|
||||
@@ -96,31 +98,14 @@ func (v *Validator) handleInternal(ctx context.Context, req types.Request) (*val
|
||||
}
|
||||
originalObject = pod
|
||||
} else {
|
||||
decoded := map[string]interface{}{}
|
||||
err := json.Unmarshal(req.AdmissionRequest.Object.Raw, &decoded)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
podMap := kube.GetPodSpec(decoded)
|
||||
if podMap == nil {
|
||||
return nil, errors.New("Object does not contain pods")
|
||||
}
|
||||
encoded, err := json.Marshal(podMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(encoded, &pod.Spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
originalObject = decoded
|
||||
pod, originalObject, err = GetObjectFromRawRequest(req.Object.Raw)
|
||||
}
|
||||
controller, err := kube.NewGenericWorkloadFromPod(pod, originalObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
controller.Kind = req.AdmissionRequest.Kind.Kind
|
||||
controllerResult, err := validator.ValidateController(&v.Config, controller)
|
||||
controllerResult, err := validator.ValidateController(ctx, &v.Config, controller)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -128,11 +113,12 @@ func (v *Validator) handleInternal(ctx context.Context, req types.Request) (*val
|
||||
}
|
||||
|
||||
// Handle for Validator to run validation checks.
|
||||
func (v *Validator) Handle(ctx context.Context, req types.Request) types.Response {
|
||||
func (v *Validator) Handle(ctx context.Context, req admission.Request) admission.Response {
|
||||
logrus.Info("Starting request")
|
||||
podResult, err := v.handleInternal(ctx, req)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error validating request: %v", err)
|
||||
return admission.ErrorResponse(http.StatusBadRequest, err)
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
allowed := true
|
||||
reason := ""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# /bin/bash
|
||||
|
||||
set -eo pipefail
|
||||
set -e
|
||||
|
||||
helm template polaris $CHARTS_DIR/stable/polaris/ \
|
||||
--namespace polaris \
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -56,7 +57,7 @@ func TestChecks(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
c, err := config.Parse([]byte("checks:\n " + tc.check + ": danger"))
|
||||
assert.NoError(t, err)
|
||||
result, err := validator.ValidateController(&c, *workload)
|
||||
result, err := validator.ValidateController(context.Background(), &c, *workload)
|
||||
assert.NoError(t, err)
|
||||
summary := result.GetSummary()
|
||||
if tc.failure {
|
||||
|
||||
+17
-14
@@ -1,12 +1,15 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
appsv1beta1 "k8s.io/api/apps/v1beta1"
|
||||
appsv1beta2 "k8s.io/api/apps/v1beta2"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
batchv1beta1 "k8s.io/api/batch/v1beta1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/dynamic"
|
||||
dynamicFake "k8s.io/client-go/dynamic/fake"
|
||||
@@ -116,39 +119,39 @@ func SetupTestAPI() (kubernetes.Interface, dynamic.Interface) {
|
||||
}
|
||||
|
||||
// SetupAddControllers creates mock controllers and adds them to the test clientset.
|
||||
func SetupAddControllers(k kubernetes.Interface, namespace string) kubernetes.Interface {
|
||||
func SetupAddControllers(ctx context.Context, k kubernetes.Interface, namespace string) kubernetes.Interface {
|
||||
d1 := MockDeploy()
|
||||
if _, err := k.AppsV1().Deployments(namespace).Create(&d1); err != nil {
|
||||
if _, err := k.AppsV1().Deployments(namespace).Create(ctx, &d1, metav1.CreateOptions{}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
s1 := MockStatefulSet()
|
||||
if _, err := k.AppsV1().StatefulSets(namespace).Create(&s1); err != nil {
|
||||
if _, err := k.AppsV1().StatefulSets(namespace).Create(ctx, &s1, metav1.CreateOptions{}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ds1 := MockDaemonSet()
|
||||
if _, err := k.AppsV1().DaemonSets(namespace).Create(&ds1); err != nil {
|
||||
if _, err := k.AppsV1().DaemonSets(namespace).Create(ctx, &ds1, metav1.CreateOptions{}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
j1 := MockJob()
|
||||
if _, err := k.BatchV1().Jobs(namespace).Create(&j1); err != nil {
|
||||
if _, err := k.BatchV1().Jobs(namespace).Create(ctx, &j1, metav1.CreateOptions{}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cj1 := MockCronJob()
|
||||
if _, err := k.BatchV1beta1().CronJobs(namespace).Create(&cj1); err != nil {
|
||||
if _, err := k.BatchV1beta1().CronJobs(namespace).Create(ctx, &cj1, metav1.CreateOptions{}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
rc1 := MockReplicationController()
|
||||
if _, err := k.CoreV1().ReplicationControllers(namespace).Create(&rc1); err != nil {
|
||||
if _, err := k.CoreV1().ReplicationControllers(namespace).Create(ctx, &rc1, metav1.CreateOptions{}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
p1 := MockNakedPod()
|
||||
if _, err := k.CoreV1().Pods(namespace).Create(&p1); err != nil {
|
||||
if _, err := k.CoreV1().Pods(namespace).Create(ctx, &p1, metav1.CreateOptions{}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -156,7 +159,7 @@ func SetupAddControllers(k kubernetes.Interface, namespace string) kubernetes.In
|
||||
}
|
||||
|
||||
// SetupAddExtraControllerVersions creates mock controllers and adds them to the test clientset.
|
||||
func SetupAddExtraControllerVersions(k kubernetes.Interface, namespace string) kubernetes.Interface {
|
||||
func SetupAddExtraControllerVersions(ctx context.Context, k kubernetes.Interface, namespace string) kubernetes.Interface {
|
||||
p := MockPod()
|
||||
|
||||
dv1b1 := appsv1beta1.Deployment{
|
||||
@@ -164,7 +167,7 @@ func SetupAddExtraControllerVersions(k kubernetes.Interface, namespace string) k
|
||||
Template: corev1.PodTemplateSpec{Spec: p.Spec},
|
||||
},
|
||||
}
|
||||
if _, err := k.AppsV1beta1().Deployments(namespace).Create(&dv1b1); err != nil {
|
||||
if _, err := k.AppsV1beta1().Deployments(namespace).Create(ctx, &dv1b1, metav1.CreateOptions{}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -173,7 +176,7 @@ func SetupAddExtraControllerVersions(k kubernetes.Interface, namespace string) k
|
||||
Template: corev1.PodTemplateSpec{Spec: p.Spec},
|
||||
},
|
||||
}
|
||||
if _, err := k.AppsV1beta2().Deployments(namespace).Create(&dv1b2); err != nil {
|
||||
if _, err := k.AppsV1beta2().Deployments(namespace).Create(ctx, &dv1b2, metav1.CreateOptions{}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -182,7 +185,7 @@ func SetupAddExtraControllerVersions(k kubernetes.Interface, namespace string) k
|
||||
Template: corev1.PodTemplateSpec{Spec: p.Spec},
|
||||
},
|
||||
}
|
||||
if _, err := k.AppsV1beta1().StatefulSets(namespace).Create(&ssv1b1); err != nil {
|
||||
if _, err := k.AppsV1beta1().StatefulSets(namespace).Create(ctx, &ssv1b1, metav1.CreateOptions{}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -191,7 +194,7 @@ func SetupAddExtraControllerVersions(k kubernetes.Interface, namespace string) k
|
||||
Template: corev1.PodTemplateSpec{Spec: p.Spec},
|
||||
},
|
||||
}
|
||||
if _, err := k.AppsV1beta2().StatefulSets(namespace).Create(&ssv1b2); err != nil {
|
||||
if _, err := k.AppsV1beta2().StatefulSets(namespace).Create(ctx, &ssv1b2, metav1.CreateOptions{}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -200,7 +203,7 @@ func SetupAddExtraControllerVersions(k kubernetes.Interface, namespace string) k
|
||||
Template: corev1.PodTemplateSpec{Spec: p.Spec},
|
||||
},
|
||||
}
|
||||
if _, err := k.AppsV1beta2().DaemonSets(namespace).Create(&dsv1b2); err != nil {
|
||||
if _, err := k.AppsV1beta2().DaemonSets(namespace).Create(ctx, &dsv1b2, metav1.CreateOptions{}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return k
|
||||
|
||||
+12
-6
@@ -2,9 +2,7 @@
|
||||
set -e
|
||||
|
||||
#sed is replacing the polaris version with this commit sha so we are testing exactly this verison.
|
||||
sed -ri "s|'(quay.io/fairwinds/polaris:).+'|'\1${CIRCLE_SHA1}'|" ./deploy/webhook.yaml
|
||||
# TODO: remove this after 1.0 is released
|
||||
sed -i "s/--webhook/webhook/" ./deploy/webhook.yaml
|
||||
sed -r "s|'(quay.io/fairwinds/polaris:).+'|'\1${CIRCLE_SHA1}'|" ./deploy/webhook.yaml > ./deploy/webhook-test.yaml
|
||||
|
||||
# Testing to ensure that the webhook starts up, allows a correct deployment to pass,
|
||||
# and prevents a incorrectly formatted deployment.
|
||||
@@ -12,8 +10,13 @@ function check_webhook_is_ready() {
|
||||
# Get the epoch time in one minute from now
|
||||
local timeout_epoch
|
||||
|
||||
# Reset another 2 minutes to wait for webhook
|
||||
timeout_epoch=$(date -d "+2 minutes" +%s)
|
||||
# Reset another 4 minutes to wait for webhook
|
||||
timeout_epoch=$(date -d "+4 minutes" +%s)
|
||||
|
||||
while ! kubectl get csr | grep -E "polaris-webhook.polaris"; do
|
||||
check_timeout "${timeout_epoch}"
|
||||
echo -n "."
|
||||
done
|
||||
|
||||
# loop until this fails (desired condition is we cannot apply this yaml doc, which means the webhook is working
|
||||
echo "Waiting for webhook to be ready"
|
||||
@@ -22,6 +25,8 @@ function check_webhook_is_ready() {
|
||||
echo -n "."
|
||||
done
|
||||
|
||||
check_timeout "${timeout_epoch}"
|
||||
|
||||
echo "Webhook started!"
|
||||
}
|
||||
|
||||
@@ -52,6 +57,7 @@ function clean_up() {
|
||||
function grab_logs() {
|
||||
kubectl -n polaris get pods -oyaml -l app=polaris
|
||||
kubectl -n polaris describe pods -l app=polaris
|
||||
kubectl -n polaris logs -l app=polaris -c webhook-certificate-generator
|
||||
kubectl -n polaris logs -l app=polaris
|
||||
}
|
||||
|
||||
@@ -60,7 +66,7 @@ kubectl create ns scale-test
|
||||
kubectl apply -n scale-test -f ./test/webhook_cases/failing_test.deployment.yaml
|
||||
|
||||
# Install the webhook
|
||||
kubectl apply -f ./deploy/webhook.yaml &> /dev/null
|
||||
kubectl apply -f ./deploy/webhook-test.yaml &> /dev/null
|
||||
|
||||
|
||||
# wait for the webhook to come online
|
||||
|
||||
Reference in New Issue
Block a user