mirror of
https://github.com/FairwindsOps/polaris.git
synced 2026-08-29 22:17:16 +00:00
Compare commits
30
Commits
rb/machine
...
7.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b90f091bb6 | ||
|
|
e3a6cb3774 | ||
|
|
7addced32c | ||
|
|
7e77350428 | ||
|
|
af0d548a07 | ||
|
|
3efa3b40c9 | ||
|
|
206322271c | ||
|
|
e5b9236268 | ||
|
|
c3b57bf6c7 | ||
|
|
652b65b3c2 | ||
|
|
41030320bb | ||
|
|
76c42c4799 | ||
|
|
65add73e70 | ||
|
|
a0000e1919 | ||
|
|
f9e2603b16 | ||
|
|
50d789fd42 | ||
|
|
25ab600eef | ||
|
|
be45519a22 | ||
|
|
ccaa384cd0 | ||
|
|
1c09ce9e09 | ||
|
|
fec00893b1 | ||
|
|
acadebe9fd | ||
|
|
a2ec025230 | ||
|
|
08682075c6 | ||
|
|
e3e790046f | ||
|
|
50319fb1b8 | ||
|
|
c3eb0811e0 | ||
|
|
5423449177 | ||
|
|
f713d43697 | ||
|
|
e896eec89f |
+3
-43
@@ -2,6 +2,7 @@ version: 2.1
|
||||
|
||||
orbs:
|
||||
rok8s: fairwinds/rok8s-scripts@11
|
||||
oss-docs: fairwinds/oss-docs@0
|
||||
|
||||
executors:
|
||||
vm:
|
||||
@@ -205,48 +206,6 @@ jobs:
|
||||
- setup_remote_docker
|
||||
- *set_environment_variables
|
||||
- *docker_build_and_push
|
||||
publish_docs:
|
||||
docker:
|
||||
- image: cimg/node:15.5.1
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Build Docs Site
|
||||
command: |
|
||||
set -e
|
||||
cd ./docs
|
||||
npm install
|
||||
npm run check-links
|
||||
npm run build
|
||||
- run:
|
||||
name: Install Tools
|
||||
command: |
|
||||
cd /tmp
|
||||
echo "Installing AWS CLI"
|
||||
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
||||
unzip awscliv2.zip
|
||||
sudo ./aws/install
|
||||
|
||||
echo "Installing Hashicorp Vault"
|
||||
curl -LO https://releases.hashicorp.com/vault/1.9.3/vault_1.9.3_linux_amd64.zip
|
||||
unzip vault_1.9.3_linux_amd64.zip
|
||||
sudo mv vault /usr/bin/vault
|
||||
sudo chmod +x /usr/bin/vault
|
||||
vault --version
|
||||
|
||||
echo "Installing yq"
|
||||
curl -LO https://github.com/mikefarah/yq/releases/download/v4.16.2/yq_linux_amd64.tar.gz
|
||||
tar -zxvf yq_linux_amd64.tar.gz
|
||||
sudo mv yq_linux_amd64 /usr/bin/yq
|
||||
sudo chmod +x /usr/bin/yq
|
||||
yq --version
|
||||
- rok8s/get_vault_env:
|
||||
vault_path: repo/polaris/env
|
||||
- run:
|
||||
name: Publish Docs Site to S3
|
||||
command: |
|
||||
cd ./dist
|
||||
aws s3 sync ./ s3://polaris.docs.fairwinds.com --delete
|
||||
workflows:
|
||||
version: 2
|
||||
|
||||
@@ -294,7 +253,8 @@ workflows:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
ignore: /^testing-.*/
|
||||
- publish_docs:
|
||||
- oss-docs/publish-docs:
|
||||
repository: polaris
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
|
||||
+2
-1
@@ -15,8 +15,9 @@ RUN go get -u github.com/gobuffalo/packr/v2/packr2
|
||||
COPY . .
|
||||
RUN packr2 build -a -o polaris *.go
|
||||
|
||||
FROM alpine:3.16.0
|
||||
FROM alpine:3.16
|
||||
WORKDIR /usr/local/bin
|
||||
RUN apk -U upgrade
|
||||
RUN apk --no-cache add ca-certificates
|
||||
|
||||
RUN addgroup -S polaris && adduser -u 1200 -S polaris -G polaris
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
successMessage: The ServiceAccount will not be automounted
|
||||
failureMessage: The ServiceAccount will be automounted
|
||||
category: Security
|
||||
target: PodSpec
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
required: ["serviceAccountName"]
|
||||
properties:
|
||||
serviceAccountName:
|
||||
type: string
|
||||
automountServiceAccountToken:
|
||||
type: boolean
|
||||
not:
|
||||
const: true
|
||||
additionalSchemaStrings:
|
||||
ServiceAccount: |
|
||||
type: object
|
||||
required:
|
||||
- metadata
|
||||
{{ if not (eq .Polaris.PodSpec.automountServiceAccountToken false) }}
|
||||
- automountServiceAccountToken
|
||||
{{ end }}
|
||||
properties:
|
||||
metadata:
|
||||
type: object
|
||||
required: ["name"]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
const: "{{ .Polaris.PodSpec.serviceAccountName }}"
|
||||
{{ if not (eq .Polaris.PodSpec.automountServiceAccountToken false) }}
|
||||
automountServiceAccountToken:
|
||||
type: boolean
|
||||
const: false
|
||||
{{ end }}
|
||||
@@ -26,12 +26,9 @@ schema:
|
||||
not:
|
||||
const: ''
|
||||
mutations:
|
||||
- op: add
|
||||
path: /resources/limits
|
||||
value: {}
|
||||
- op: add
|
||||
path: /resources/limits/cpu
|
||||
value: 100m
|
||||
comments:
|
||||
- find: "cpu: 100m"
|
||||
comment: "TODO: Set this to the amount of CPU you want to reserve for your workload"
|
||||
comment: "TODO: Set this to the maximum amount of CPU you want your workload to use"
|
||||
|
||||
@@ -26,12 +26,9 @@ schema:
|
||||
not:
|
||||
const: ''
|
||||
mutations:
|
||||
- op: add
|
||||
path: /resources/requests
|
||||
value: {}
|
||||
- op: add
|
||||
path: /resources/requests/cpu
|
||||
value: 100m
|
||||
comments:
|
||||
- find: "cpu: 100m"
|
||||
comment: "TODO: Set this to the amount of CPU you want to reserve for your workload"
|
||||
comment: "TODO: Set this to the amount of CPU you want to reserve for your workload"
|
||||
|
||||
@@ -24,3 +24,8 @@ schema:
|
||||
- not:
|
||||
contains:
|
||||
pattern: '^(?i)NET_ADMIN$'
|
||||
|
||||
|
||||
mutations:
|
||||
- op: remove
|
||||
path: /securityContext/capabilities
|
||||
|
||||
@@ -54,3 +54,9 @@ schema:
|
||||
pattern: '^(?i)KILL$'
|
||||
- contains:
|
||||
pattern: '^(?i)AUDIT_WRITE$'
|
||||
mutations:
|
||||
- op: remove
|
||||
path: /securityContext/capabilities
|
||||
- op: add
|
||||
path: /securityContext/capabilities
|
||||
value: {"drop": ["ALL"]}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
successMessage: One of AppArmor, Seccomp, SELinux, or dropping Linux Capabilities are used to restrict containers using unwanted privileges
|
||||
FailureMessage: Use one of AppArmor, Seccomp, SELinux, or dropping Linux Capabilities to restrict containers using unwanted privileges
|
||||
category: Security
|
||||
target: Container
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
definitions:
|
||||
podOrContainerSeccompProfile:
|
||||
type: object
|
||||
{{ $podSeccompProfileType := .Polaris.PodSpec.securityContext.seccompProfile.type }}
|
||||
required:
|
||||
{{ if or (not $podSeccompProfileType) (eq $podSeccompProfileType "Unconfined") }}
|
||||
- securityContext
|
||||
{{ end }}
|
||||
properties:
|
||||
securityContext:
|
||||
type: object
|
||||
required:
|
||||
{{ if or (not $podSeccompProfileType) (eq $podSeccompProfileType "Unconfined") }}
|
||||
- seccompProfile
|
||||
{{ end }}
|
||||
properties:
|
||||
seccompProfile:
|
||||
type: object
|
||||
required:
|
||||
{{ if or (not $podSeccompProfileType) (eq $podSeccompProfileType "Unconfined") }}
|
||||
- type
|
||||
{{ end }}
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
allOf:
|
||||
- not:
|
||||
const: "Unconfined"
|
||||
{{ if or (not $podSeccompProfileType) (eq $podSeccompProfileType "Unconfined") }}
|
||||
- minLength: 1
|
||||
{{ end }}
|
||||
podOrContainerSELinuxOptions:
|
||||
type: object
|
||||
{{ $podSELinuxOptions := .Polaris.PodSpec.securityContext.seLinuxOptions }}
|
||||
{{ if not $podSELinuxOptions }}
|
||||
required: ["securityContext"]
|
||||
properties:
|
||||
securityContext:
|
||||
type: object
|
||||
required: ["seLinuxOptions"]
|
||||
properties:
|
||||
seLinuxOptions:
|
||||
type: object
|
||||
minProperties: 1
|
||||
{{ end }}
|
||||
containerDropCapabilities:
|
||||
type: object
|
||||
required: ["securityContext"]
|
||||
properties:
|
||||
securityContext:
|
||||
type: object
|
||||
required: ["capabilities"]
|
||||
properties:
|
||||
capabilities:
|
||||
type: object
|
||||
required: ["drop"]
|
||||
properties:
|
||||
drop:
|
||||
type: array
|
||||
minItems: 1
|
||||
add:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
not:
|
||||
pattern: '^(?i)ALL$'
|
||||
# End of definitions
|
||||
{{/* Check for AppArmor which uses pod annotations. IF pod fields are missing,
|
||||
require one of the other hardening measures. */}}
|
||||
{{ $annotationName := (print "container.apparmor.security.beta.kubernetes.io/" .Polaris.Container.name) }}
|
||||
{{/* Checking annotations before using index() avoids a nil panic when there are no annotations */}}
|
||||
{{ $annotationExists := false }}
|
||||
{{ if .Polaris.PodTemplate.metadata.annotations }}
|
||||
{{ $annotationExists = index .Polaris "PodTemplate" "metadata" "annotations" $annotationName }}
|
||||
{{ end }}
|
||||
{{ if $annotationExists }}
|
||||
type: object
|
||||
{{ else }}
|
||||
anyOf:
|
||||
- $ref: "#/definitions/podOrContainerSeccompProfile"
|
||||
- $ref: "#/definitions/podOrContainerSELinuxOptions"
|
||||
- $ref: "#/definitions/containerDropCapabilities"
|
||||
{{ end}}
|
||||
@@ -26,12 +26,9 @@ schema:
|
||||
not:
|
||||
const: ''
|
||||
mutations:
|
||||
- op: add
|
||||
path: /resources/limits
|
||||
value: {}
|
||||
- op: add
|
||||
path: /resources/limits/memory
|
||||
value: "512Mi"
|
||||
comments:
|
||||
- find: "memory: 512Mi"
|
||||
comment: "TODO: Set this to the amount of Memory you want to reserve for your workload"
|
||||
comment: "TODO: Set this to the maximum amount of memory you want your workload to use"
|
||||
|
||||
@@ -26,12 +26,9 @@ schema:
|
||||
not:
|
||||
const: ''
|
||||
mutations:
|
||||
- op: add
|
||||
path: /resources/requests
|
||||
value: {}
|
||||
- op: add
|
||||
path: /resources/requests/memory
|
||||
value: "512Mi"
|
||||
comments:
|
||||
- find: "memory: 512Mi"
|
||||
comment: "TODO: Set this to the amount of Memory you want to reserve for your workload"
|
||||
comment: "TODO: Set this to the amount of Memory you want to reserve for your workload"
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
successMessage: A NetworkPolicy matches pod labels and contains egress and ingress rules
|
||||
failureMessage: A NetworkPolicy should match pod labels and contain applied egress and ingress rules
|
||||
category: Security
|
||||
target: PodTemplate
|
||||
schema:
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
properties:
|
||||
metadata:
|
||||
type: object
|
||||
properties:
|
||||
labels:
|
||||
type: object
|
||||
minProperties: 1
|
||||
additionalSchemaStrings:
|
||||
networking.k8s.io/NetworkPolicy: |
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
required: ["podSelector", "egress", "ingress"]
|
||||
properties:
|
||||
podSelector:
|
||||
type: object
|
||||
required: ["matchLabels"]
|
||||
properties:
|
||||
matchLabels:
|
||||
type: object
|
||||
oneOf:
|
||||
{{ range $key, $value := .Polaris.PodTemplate.metadata.labels }}
|
||||
- properties:
|
||||
"{{ $key }}":
|
||||
type: string
|
||||
const: {{ $value }}
|
||||
required: ["{{ $key }}"]
|
||||
{{ end }}
|
||||
egress:
|
||||
type: array
|
||||
minItems: 1
|
||||
ingress:
|
||||
minItems: 1
|
||||
type: array
|
||||
policyTypes:
|
||||
type: array
|
||||
allOf:
|
||||
- contains:
|
||||
pattern: '^(?i)Egress$'
|
||||
- contains:
|
||||
pattern: '^(?i)Ingress$'
|
||||
@@ -40,4 +40,9 @@ schema:
|
||||
- securityContext
|
||||
properties:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
mutations:
|
||||
- op: add
|
||||
path: /securityContext/readOnlyRootFilesystem
|
||||
value: true
|
||||
|
||||
|
||||
@@ -51,6 +51,6 @@ schema:
|
||||
securityContext:
|
||||
$ref: "#/definitions/goodSecurityContext"
|
||||
mutations:
|
||||
- op: replace
|
||||
- op: add
|
||||
path: /securityContext/runAsNonRoot
|
||||
value: true
|
||||
value: true
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
successMessage: The ConfigMap does not contain potentially sensitive content in its keys and values
|
||||
failureMessage: Potentially sensitive content is detected in the ConfigMap keys or values
|
||||
category: Security
|
||||
target: /ConfigMap
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
required: ["metadata"]
|
||||
properties:
|
||||
metadata:
|
||||
required: ["name"]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
data:
|
||||
type: object
|
||||
propertyNames:
|
||||
'$comment': These ConfigMap keys will be disallowed.
|
||||
allOf:
|
||||
- not:
|
||||
pattern: '(?i)^AWS_SECRET_ACCESS_KEY$'
|
||||
- not:
|
||||
pattern: '(?i)^GOOGLE_APPLICATION_CREDENTIALS$'
|
||||
- not:
|
||||
pattern: '(?i)^AZURE_.+KEY$'
|
||||
- not:
|
||||
pattern: '(?i)^OCI_CLI_KEY_CONTENT$'
|
||||
- not:
|
||||
pattern: '(?i)password'
|
||||
- not:
|
||||
pattern: '(?i)token'
|
||||
- not:
|
||||
pattern: '(?i)bearer'
|
||||
- not:
|
||||
pattern: '(?i)secret'
|
||||
'$comment': This allows ConfigMap keys not excluded above.
|
||||
- pattern: '(?i).*'
|
||||
additionalProperties:
|
||||
'$comment': These ConfigMap values will be disallowed.
|
||||
allOf:
|
||||
- not:
|
||||
'$comment': THis matches variations like begin private key, begin rsa private key ...
|
||||
pattern: '(?i)\s*-BEGIN\s+.*PRIVATE KEY-\s*'
|
||||
@@ -0,0 +1,43 @@
|
||||
successMessage: The container does not set potentially sensitive environment variables
|
||||
failureMessage: The container sets potentially sensitive environment variables
|
||||
category: Security
|
||||
target: Container
|
||||
schemaString: |
|
||||
'$schema': http://json-schema.org/draft-07/schema
|
||||
type: object
|
||||
properties:
|
||||
env:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: ["name"]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
'$comment': These environment variable names will be disallowed.
|
||||
allOf:
|
||||
- not:
|
||||
pattern: '(?i)^AWS_SECRET_ACCESS_KEY$'
|
||||
- not:
|
||||
pattern: '(?i)^GOOGLE_APPLICATION_CREDENTIALS$'
|
||||
- not:
|
||||
pattern: '(?i)^AZURE_.+KEY$'
|
||||
- not:
|
||||
pattern: '(?i)^OCI_CLI_KEY_CONTENT$'
|
||||
- not:
|
||||
pattern: '(?i)password'
|
||||
- not:
|
||||
pattern: '(?i)token'
|
||||
- not:
|
||||
pattern: '(?i)bearer'
|
||||
- not:
|
||||
pattern: '(?i)secret'
|
||||
'$comment': This allows variable names not excluded above.
|
||||
- pattern: '(?i).*'
|
||||
value:
|
||||
type: string
|
||||
'$comment': These environment variable values will be disallowed.
|
||||
allOf:
|
||||
- not:
|
||||
'$comment': THis matches variations like begin private key, begin rsa private key ...
|
||||
pattern: '(?i)\s*-BEGIN\s+.*PRIVATE KEY-\s*'
|
||||
+24
-11
@@ -33,11 +33,16 @@ import (
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
var filesPath string
|
||||
var (
|
||||
filesPath string
|
||||
checksToFix []string
|
||||
fixAll bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(fixCommand)
|
||||
fixCommand.PersistentFlags().StringVar(&filesPath, "files-path", "", "mutate and fix one or more YAML files in a specified folder")
|
||||
fixCommand.PersistentFlags().StringSliceVar(&checksToFix, "checks", []string{}, "Optional flag to specify specific checks to fix eg. checks=hostIPCSet,hostPIDSet and checks=all applies fix to all defined checks mutations")
|
||||
}
|
||||
|
||||
var fixCommand = &cobra.Command{
|
||||
@@ -73,12 +78,24 @@ var fixCommand = &cobra.Command{
|
||||
}
|
||||
var contentStr string
|
||||
isFirstResource := true
|
||||
|
||||
if len(checksToFix) > 0 {
|
||||
if len(checksToFix) == 1 && checksToFix[0] == "all" {
|
||||
allchecks := []string{}
|
||||
for key := range config.Checks {
|
||||
allchecks = append(allchecks, key)
|
||||
}
|
||||
config.Mutations = allchecks
|
||||
} else {
|
||||
config.Mutations = checksToFix
|
||||
}
|
||||
}
|
||||
|
||||
for _, fullFilePath := range yamlFiles {
|
||||
|
||||
yamlFile, err := ioutil.ReadFile(fullFilePath)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error reading file with file path %s: %v", fullFilePath, err)
|
||||
os.Exit(1)
|
||||
logrus.Fatalf("Error reading file with file path %s: %v", fullFilePath, err)
|
||||
}
|
||||
|
||||
dec := yamlV3.NewDecoder(bytes.NewReader(yamlFile))
|
||||
@@ -95,19 +112,16 @@ var fixCommand = &cobra.Command{
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
logrus.Errorf("Error decoding data for file with file path %s: %v", fullFilePath, err)
|
||||
os.Exit(1)
|
||||
logrus.Fatalf("Error decoding data for file with file path %s: %v", fullFilePath, err)
|
||||
}
|
||||
yamlContent, err := yamlV3.Marshal(data)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error marshalling %s: %v", fullFilePath, err)
|
||||
os.Exit(1)
|
||||
logrus.Fatalf("Error marshalling %s: %v", fullFilePath, err)
|
||||
}
|
||||
kubeResources := kube.CreateResourceProviderFromYaml(string(yamlContent))
|
||||
results, err := validator.ApplyAllSchemaChecksToResourceProvider(&config, kubeResources)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error applying schema check to the resources %s: %v", fullFilePath, err)
|
||||
os.Exit(1)
|
||||
logrus.Fatalf("Error applying schema check to the resources %s: %v", fullFilePath, err)
|
||||
}
|
||||
comments, allMutations := mutation.GetMutationsAndCommentsFromResults(results)
|
||||
updatedYamlContent := string(yamlContent)
|
||||
@@ -142,8 +156,7 @@ var fixCommand = &cobra.Command{
|
||||
if contentStr != "" {
|
||||
err = ioutil.WriteFile(fullFilePath, []byte(contentStr), 0644)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error writing output to file: %v", err)
|
||||
os.Exit(1)
|
||||
logrus.Fatalf("Error writing output to file: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-5
@@ -29,11 +29,15 @@ import (
|
||||
|
||||
var webhookPort int
|
||||
var disableWebhookConfigInstaller bool
|
||||
var enableMutations bool
|
||||
var enableValidations bool
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(webhookCmd)
|
||||
webhookCmd.PersistentFlags().IntVarP(&webhookPort, "port", "p", 9876, "Port for the dashboard webserver.")
|
||||
webhookCmd.PersistentFlags().BoolVar(&disableWebhookConfigInstaller, "disable-webhook-config-installer", false, "disable the installer in the webhook server, so it won't install webhook configuration resources during bootstrapping.")
|
||||
webhookCmd.PersistentFlags().BoolVar(&disableWebhookConfigInstaller, "disable-webhook-config-installer", false, "Disable the installer in the webhook server, so it won't install webhook configuration resources during bootstrapping.")
|
||||
webhookCmd.PersistentFlags().BoolVar(&enableValidations, "validate", true, "Enable the validating webhook to reject workloads with issues")
|
||||
webhookCmd.PersistentFlags().BoolVar(&enableMutations, "mutate", false, "Enable the mutating webhook to modify workloads with issues")
|
||||
}
|
||||
|
||||
var webhookCmd = &cobra.Command{
|
||||
@@ -61,11 +65,17 @@ var webhookCmd = &cobra.Command{
|
||||
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
|
||||
fwebhook.NewValidateWebhook(mgr, fwebhook.Validator{Config: config, Client: mgr.GetClient()})
|
||||
fwebhook.NewMutateWebhook(mgr, fwebhook.Mutator{Config: config, Client: mgr.GetClient()})
|
||||
if !enableMutations && !enableValidations {
|
||||
logrus.Errorf("One of --mutate or --validate must be set to true")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if enableValidations {
|
||||
fwebhook.NewValidateWebhook(mgr, fwebhook.Validator{Config: config, Client: mgr.GetClient()})
|
||||
}
|
||||
if enableMutations {
|
||||
fwebhook.NewMutateWebhook(mgr, fwebhook.Mutator{Config: config, Client: mgr.GetClient()})
|
||||
}
|
||||
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)
|
||||
|
||||
@@ -45,3 +45,17 @@ output unless we are rejecting a workload altogether.
|
||||
This means that any checks with a severity of `warning` will still pass webhook validation,
|
||||
and the only evidence of that warning will either be in the Polaris dashboard or the
|
||||
Polaris webhook logs. This will change in a future version of Kubernetes.
|
||||
|
||||
## Mutating Webhook
|
||||
By default, the Admission Controller is just pass/fail, but
|
||||
Polaris can also operate as a mutating webhook for many of the issues it checks for.
|
||||
This means Polaris will remediate the issue it finds, rather than rejecting
|
||||
the deployment.
|
||||
|
||||
To enable the mutating webhook, add `--set webhook.mutate=true` to your
|
||||
Helm instlallation command.
|
||||
|
||||
By default, the only mutation enabled is `pullPolicyNotAlways`. If you'd like to
|
||||
enable other mutations, you can set the `webhook.mutations` flag.
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,18 @@ meta:
|
||||
content: "Fairwinds Polaris | Changelog"
|
||||
|
||||
---
|
||||
## 7.0.1
|
||||
* Documentation updates
|
||||
|
||||
## 7.0.0
|
||||
* Better support for `polaris fix`
|
||||
* `target: Pod` is now `target: PodSpec` (to differentiate naked Pods from Controllers)
|
||||
|
||||
## 6.0.0
|
||||
* Preliminary support for `polaris fix` command
|
||||
* Changes to how Pod owners are determined
|
||||
* Removed YAML manifests from the `deploy/` directory - Helm is now the default install mechanism
|
||||
|
||||
## 5.1.0
|
||||
* Support `--context` flag for kubecontext
|
||||
* Treat core Kubernetes controllers (`Deployments`, `StatefulSets`, `DaemonSets`, `CronJobs`) as top-level objects, instead of following owner refs
|
||||
|
||||
@@ -44,9 +44,10 @@ check ID. Note that you'll also have to set its severity in the `checks` section
|
||||
* `target` - specifies the type of resource to check. This can be:
|
||||
* a group and kind, e.g. `apps/Deployment` or `networking.k8s.io/Ingress`
|
||||
* `Controller`, to check _any_ resource that creates Pods (e.g. Deployments, CronJobs, StatefulSets), as well as naked Pods
|
||||
* `PodTemplate`, same as `Controller`, but the schema applies to the Pod template rather than the top-level controller
|
||||
* `PodSpec`, same as `Controller`, but the schema applies to the Pod spec rather than the top-level controller
|
||||
* `Container` same as `Controller`, but the schema applies to all Container specs rather than the top-level controller
|
||||
* `controllers` - if `target` is `Controller`, `Pod` or `Container`, you can use this to change which types of controllers are checked
|
||||
* `controllers` - if `target` is `Controller`, `PodSpec` or `Container`, you can use this to change which types of controllers are checked
|
||||
* `controllers.include` - _only_ check these controllers
|
||||
* `controllers.exclude` - check all controllers except these
|
||||
* `containers` - if `target` is `Container`, you can use this to decide if `initContainers`, `containers`, or both should be checked
|
||||
@@ -134,6 +135,11 @@ schema:
|
||||
const: "{{ .metadata.name }}"
|
||||
```
|
||||
|
||||
* The object available via the go template is the full object, and not limited by `target`.
|
||||
* A check of `target: PodSpec` can directly access the pod specification via the go template variable `.Polaris.PodSpec`.
|
||||
* A check of `target: PodTemplate` can directly access the pod template via the go template variable `.Polaris.PodTemplate`.
|
||||
* A check of `target: Container` can directly access the container being checked via the go template variable `.Polaris.container`. The pod template and pod specification can also be accessed via the respective variables `.Polaris.PodTemplate` and `.Polaris.PodSpec`. Access to pod-level fields allows a container check to consult related fields from the pod, such as `securityContext`.
|
||||
|
||||
You can also use the full [Go template syntax](https://golang.org/pkg/text/template/), though
|
||||
you may need to specify your schema as a string in order to use concepts like `range`. E.g.
|
||||
this check ensures that at least one of the object's labels is present in `matchLabels`:
|
||||
|
||||
@@ -25,6 +25,32 @@ brew install FairwindsOps/tap/polaris
|
||||
polaris version
|
||||
```
|
||||
|
||||
## Checking Infrastructure as Code files
|
||||
You can audit Kubernetes YAML files by running:
|
||||
```bash
|
||||
polaris audit --audit-path ./deploy/ --format=pretty
|
||||
```
|
||||
This will print out any issues Polaris finds in your manifests.
|
||||
|
||||
Polaris can only check raw YAML manifests. If you'd like to check a Helm template,
|
||||
you can run `helm template` to generate a manifest that Polaris can check.
|
||||
|
||||
## Fixing Issues
|
||||
Polaris can automatically fix many of the issues it finds. For example, you can run
|
||||
```bash
|
||||
polaris fix --files-path ./deploy/ --checks=all
|
||||
```
|
||||
|
||||
to fix any issues inside the `deploy` directory. Polaris may leave
|
||||
comments next to some changes (e.g. liveness and readiness probes) prompting
|
||||
the user to set them to something more appropriate given the context of their
|
||||
application.
|
||||
|
||||
Note that not all issues can be automatically fixed.
|
||||
|
||||
Currently only raw YAML manifests can be mutated. Helm charts etc.
|
||||
still need to be changed manually.
|
||||
|
||||
## Running in a CI pipeline
|
||||
|
||||
### Set minimum score for an exit code
|
||||
|
||||
@@ -12,8 +12,11 @@ checks:
|
||||
memoryRequestsMissing: warning
|
||||
memoryLimitsMissing: warning
|
||||
# security
|
||||
automountServiceAccountToken: warning
|
||||
hostIPCSet: danger
|
||||
hostPIDSet: danger
|
||||
linuxHardening: danger
|
||||
missingNetworkPolicy: warning
|
||||
notReadOnlyRootFilesystem: warning
|
||||
privilegeEscalationAllowed: danger
|
||||
runAsRootAllowed: danger
|
||||
@@ -22,6 +25,8 @@ checks:
|
||||
insecureCapabilities: warning
|
||||
hostNetworkSet: danger
|
||||
hostPortSet: warning
|
||||
sensitiveContainerEnvVar: danger
|
||||
sensitiveConfigmapContent: danger
|
||||
# custom
|
||||
resourceLimits: warning
|
||||
imageRegistry: danger
|
||||
|
||||
@@ -27,7 +27,10 @@ checks:
|
||||
hostNetworkSet: danger
|
||||
hostPortSet: warning
|
||||
tlsSettingsMissing: warning
|
||||
|
||||
|
||||
mutations:
|
||||
- pullPolicyNotAlways
|
||||
|
||||
exemptions:
|
||||
- namespace: kube-system
|
||||
controllerNames:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
options:
|
||||
organization: fairwinds-opensource
|
||||
organization: fairwinds-production
|
||||
baseBranch: master
|
||||
|
||||
images:
|
||||
|
||||
@@ -8,18 +8,18 @@ require (
|
||||
github.com/gobuffalo/packr/v2 v2.8.3
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/qri-io/jsonschema v0.1.1
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.4.0
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cobra v1.5.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.7.1
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/thoas/go-funk v0.9.2
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.24.1
|
||||
k8s.io/apimachinery v0.24.1
|
||||
k8s.io/client-go v0.24.1
|
||||
sigs.k8s.io/controller-runtime v0.12.1
|
||||
k8s.io/api v0.24.3
|
||||
k8s.io/apimachinery v0.24.3
|
||||
k8s.io/client-go v0.24.3
|
||||
sigs.k8s.io/controller-runtime v0.12.3
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
)
|
||||
|
||||
@@ -78,13 +78,13 @@ require (
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220524215830-622c5d57e401 // indirect
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
||||
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect
|
||||
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
k8s.io/component-base v0.24.1 // indirect
|
||||
k8s.io/component-base v0.24.2 // indirect
|
||||
k8s.io/klog/v2 v2.60.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20220603121420-31174f50af60 // indirect
|
||||
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
|
||||
|
||||
@@ -134,6 +134,7 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -525,8 +526,9 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
@@ -539,8 +541,9 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
|
||||
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
|
||||
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
|
||||
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
|
||||
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
|
||||
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
@@ -551,14 +554,16 @@ github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH
|
||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/thoas/go-funk v0.9.2 h1:oKlNYv0AY5nyf9g+/GhMgS/UO2ces0QRdPKwkhY3VCk=
|
||||
github.com/thoas/go-funk v0.9.2/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=
|
||||
@@ -841,8 +846,9 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM=
|
||||
@@ -1140,22 +1146,21 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/api v0.24.0/go.mod h1:5Jl90IUrJHUJYEMANRURMiVvJ0g7Ax7r3R1bqO8zx8I=
|
||||
k8s.io/api v0.24.1 h1:BjCMRDcyEYz03joa3K1+rbshwh1Ay6oB53+iUx2H8UY=
|
||||
k8s.io/api v0.24.1/go.mod h1:JhoOvNiLXKTPQ60zh2g0ewpA+bnEYf5q44Flhquh4vQ=
|
||||
k8s.io/apiextensions-apiserver v0.24.0 h1:JfgFqbA8gKJ/uDT++feAqk9jBIwNnL9YGdQvaI9DLtY=
|
||||
k8s.io/apiextensions-apiserver v0.24.0/go.mod h1:iuVe4aEpe6827lvO6yWQVxiPSpPoSKVjkq+MIdg84cM=
|
||||
k8s.io/apimachinery v0.24.0/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM=
|
||||
k8s.io/apimachinery v0.24.1 h1:ShD4aDxTQKN5zNf8K1RQ2u98ELLdIW7jEnlO9uAMX/I=
|
||||
k8s.io/apimachinery v0.24.1/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM=
|
||||
k8s.io/apiserver v0.24.0/go.mod h1:WFx2yiOMawnogNToVvUYT9nn1jaIkMKj41ZYCVycsBA=
|
||||
k8s.io/client-go v0.24.0/go.mod h1:VFPQET+cAFpYxh6Bq6f4xyMY80G6jKKktU6G0m00VDw=
|
||||
k8s.io/client-go v0.24.1 h1:w1hNdI9PFrzu3OlovVeTnf4oHDt+FJLd9Ndluvnb42E=
|
||||
k8s.io/client-go v0.24.1/go.mod h1:f1kIDqcEYmwXS/vTbbhopMUbhKp2JhOeVTfxgaCIlF8=
|
||||
k8s.io/code-generator v0.24.0/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w=
|
||||
k8s.io/component-base v0.24.0/go.mod h1:Dgazgon0i7KYUsS8krG8muGiMVtUZxG037l1MKyXgrA=
|
||||
k8s.io/component-base v0.24.1 h1:APv6W/YmfOWZfo+XJ1mZwep/f7g7Tpwvdbo9CQLDuts=
|
||||
k8s.io/component-base v0.24.1/go.mod h1:DW5vQGYVCog8WYpNob3PMmmsY8A3L9QZNg4j/dV3s38=
|
||||
k8s.io/api v0.24.2/go.mod h1:AHqbSkTm6YrQ0ObxjO3Pmp/ubFF/KuM7jU+3khoBsOg=
|
||||
k8s.io/api v0.24.3 h1:tt55QEmKd6L2k5DP6G/ZzdMQKvG5ro4H4teClqm0sTY=
|
||||
k8s.io/api v0.24.3/go.mod h1:elGR/XSZrS7z7cSZPzVWaycpJuGIw57j9b95/1PdJNI=
|
||||
k8s.io/apiextensions-apiserver v0.24.2 h1:/4NEQHKlEz1MlaK/wHT5KMKC9UKYz6NZz6JE6ov4G6k=
|
||||
k8s.io/apiextensions-apiserver v0.24.2/go.mod h1:e5t2GMFVngUEHUd0wuCJzw8YDwZoqZfJiGOW6mm2hLQ=
|
||||
k8s.io/apimachinery v0.24.2/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM=
|
||||
k8s.io/apimachinery v0.24.3 h1:hrFiNSA2cBZqllakVYyH/VyEh4B581bQRmqATJSeQTg=
|
||||
k8s.io/apimachinery v0.24.3/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM=
|
||||
k8s.io/apiserver v0.24.2/go.mod h1:pSuKzr3zV+L+MWqsEo0kHHYwCo77AT5qXbFXP2jbvFI=
|
||||
k8s.io/client-go v0.24.2/go.mod h1:zg4Xaoo+umDsfCWr4fCnmLEtQXyCNXCvJuSsglNcV30=
|
||||
k8s.io/client-go v0.24.3 h1:Nl1840+6p4JqkFWEW2LnMKU667BUxw03REfLAVhuKQY=
|
||||
k8s.io/client-go v0.24.3/go.mod h1:AAovolf5Z9bY1wIg2FZ8LPQlEdKHjLI7ZD4rw920BJw=
|
||||
k8s.io/code-generator v0.24.2/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w=
|
||||
k8s.io/component-base v0.24.2 h1:kwpQdoSfbcH+8MPN4tALtajLDfSfYxBDYlXobNWI6OU=
|
||||
k8s.io/component-base v0.24.2/go.mod h1:ucHwW76dajvQ9B7+zecZAP3BVqvrHoOxm8olHEg0nmM=
|
||||
k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
|
||||
k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
|
||||
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
|
||||
@@ -1172,8 +1177,8 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lRTdivWO2qYVCVG7dEADOMo/MLDCVr8So2g88Uw=
|
||||
sigs.k8s.io/controller-runtime v0.12.1 h1:4BJY01xe9zKQti8oRjj/NeHKRXthf1YkYJAgLONFFoI=
|
||||
sigs.k8s.io/controller-runtime v0.12.1/go.mod h1:BKhxlA4l7FPK4AQcsuL4X6vZeWnKDXez/vp1Y8dxTU0=
|
||||
sigs.k8s.io/controller-runtime v0.12.3 h1:FCM8xeY/FI8hoAfh/V4XbbYMY20gElh9yh+A98usMio=
|
||||
sigs.k8s.io/controller-runtime v0.12.3/go.mod h1:qKsk4WE6zW2Hfj0G4v10EnNB2jMG1C+NTb8h+DwCoU0=
|
||||
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY=
|
||||
sigs.k8s.io/json v0.0.0-20220525155127-227cbc7cc124 h1:2sgAQQcY0dEW2SsQwTXhQV4vO6+rSslYx8K3XmM5hqQ=
|
||||
sigs.k8s.io/json v0.0.0-20220525155127-227cbc7cc124/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY=
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
cmd "github.com/fairwindsops/polaris/cmd/polaris"
|
||||
)
|
||||
|
||||
const (
|
||||
var (
|
||||
// Version is set during build
|
||||
Version = "development"
|
||||
// Commit is set during build
|
||||
|
||||
@@ -32,6 +32,7 @@ var (
|
||||
"hostIPCSet",
|
||||
"hostPIDSet",
|
||||
"hostNetworkSet",
|
||||
"automountServiceAccountToken",
|
||||
// Container checks
|
||||
"memoryLimitsMissing",
|
||||
"memoryRequestsMissing",
|
||||
@@ -49,11 +50,15 @@ var (
|
||||
"dangerousCapabilities",
|
||||
"insecureCapabilities",
|
||||
"priorityClassNotSet",
|
||||
"linuxHardening",
|
||||
"sensitiveContainerEnvVar",
|
||||
// Other checks
|
||||
"tlsSettingsMissing",
|
||||
"pdbDisruptionsIsZero",
|
||||
"metadataAndNameMismatched",
|
||||
"missingPodDisruptionBudget",
|
||||
"missingNetworkPolicy",
|
||||
"sensitiveConfigmapContent",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ const (
|
||||
TargetContainer TargetKind = "Container"
|
||||
// TargetPodSpec points to the pod spec
|
||||
TargetPodSpec TargetKind = "PodSpec"
|
||||
// TargetPodTemplate points to the pod template
|
||||
TargetPodTemplate TargetKind = "PodTemplate"
|
||||
)
|
||||
|
||||
// HandledTargets is a list of target names that are explicitly handled
|
||||
@@ -48,6 +50,7 @@ var HandledTargets = []TargetKind{
|
||||
TargetController,
|
||||
TargetContainer,
|
||||
TargetPodSpec,
|
||||
TargetPodTemplate,
|
||||
}
|
||||
|
||||
// MutationComment is the comments added to a mutated file
|
||||
@@ -258,6 +261,11 @@ func (check SchemaCheck) CheckPodSpec(pod *corev1.PodSpec) (bool, []jsonschema.V
|
||||
return check.CheckObject(pod)
|
||||
}
|
||||
|
||||
// CheckPodTemplate checks a pod template against the schema
|
||||
func (check SchemaCheck) CheckPodTemplate(podTemplate interface{}) (bool, []jsonschema.ValError, error) {
|
||||
return check.CheckObject(podTemplate)
|
||||
}
|
||||
|
||||
// CheckController checks a controler's spec against the schema
|
||||
func (check SchemaCheck) CheckController(bytes []byte) (bool, []jsonschema.ValError, error) {
|
||||
errs, err := check.Validator.ValidateBytes(bytes)
|
||||
@@ -304,6 +312,11 @@ func (check SchemaCheck) CheckAdditionalObjects(groupkind string, objects []inte
|
||||
// IsActionable decides if this check applies to a particular target
|
||||
func (check SchemaCheck) IsActionable(target TargetKind, kind string, isInit bool) bool {
|
||||
if funk.Contains(HandledTargets, target) {
|
||||
if check.Target == TargetPodTemplate && target == TargetPodSpec {
|
||||
// A target=PodSpec and check.Target=PodTemplate is expected
|
||||
// because applyPodSchemaChecks() explicitly sets check.Target
|
||||
return true
|
||||
}
|
||||
if check.Target != target {
|
||||
return false
|
||||
}
|
||||
|
||||
+44
-3
@@ -35,6 +35,7 @@ type GenericResource struct {
|
||||
ObjectMeta kubeAPIMetaV1.Object
|
||||
Resource unstructured.Unstructured
|
||||
PodSpec *kubeAPICoreV1.PodSpec
|
||||
PodTemplate interface{}
|
||||
OriginalObjectJSON []byte
|
||||
}
|
||||
|
||||
@@ -53,6 +54,10 @@ func NewGenericResourceFromUnstructured(unst unstructured.Unstructured, podSpecM
|
||||
return workload, err
|
||||
}
|
||||
workload.ObjectMeta = objMeta
|
||||
workload.PodTemplate, err = GetPodTemplate(unst.UnstructuredContent())
|
||||
if err != nil {
|
||||
return workload, err
|
||||
}
|
||||
|
||||
b, err := json.Marshal(&unst)
|
||||
if err != nil {
|
||||
@@ -84,10 +89,15 @@ func NewGenericResourceFromUnstructured(unst unstructured.Unstructured, podSpecM
|
||||
|
||||
// NewGenericResourceFromPod builds a new workload for a given Pod without looking at parents
|
||||
func NewGenericResourceFromPod(podResource kubeAPICoreV1.Pod, originalObject interface{}) (GenericResource, error) {
|
||||
podMap, err := SerializePod(&podResource)
|
||||
if err != nil {
|
||||
return GenericResource{}, err
|
||||
}
|
||||
workload := GenericResource{
|
||||
Kind: "Pod",
|
||||
PodSpec: &podResource.Spec,
|
||||
ObjectMeta: podResource.ObjectMeta.GetObjectMeta(),
|
||||
Kind: "Pod",
|
||||
PodSpec: &podResource.Spec,
|
||||
PodTemplate: podMap,
|
||||
ObjectMeta: podResource.ObjectMeta.GetObjectMeta(),
|
||||
}
|
||||
if originalObject != nil {
|
||||
bytes, err := json.Marshal(originalObject)
|
||||
@@ -237,3 +247,34 @@ func GetPodSpec(yaml map[string]interface{}) interface{} {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPodTemplate looks inside arbitrary YAML for a Pod template, containing
|
||||
// fields `spec.containers`.
|
||||
// For example, it returns the `spec.template` level of a Kubernetes Deployment yaml.
|
||||
func GetPodTemplate(yaml map[string]interface{}) (podTemplate interface{}, err error) {
|
||||
if yamlSpec, ok := yaml["spec"]; ok {
|
||||
if yamlSpecMap, ok := yamlSpec.(map[string]interface{}); ok {
|
||||
if _, ok := yamlSpecMap["containers"]; ok {
|
||||
// This is a hack around unstructured.SetNestedField using DeepCopy which does
|
||||
// not support the type int, and panics.
|
||||
// Related: https://github.com/kubernetes/kubernetes/issues/62769
|
||||
podTemplateJSON, err := json.Marshal(yaml)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
podTemplateMap := make(map[string]interface{})
|
||||
err = json.Unmarshal(podTemplateJSON, &podTemplateMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return podTemplateMap, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, podSpecField := range podSpecFields {
|
||||
if childYaml, ok := yaml[podSpecField]; ok {
|
||||
return GetPodTemplate(childYaml.(map[string]interface{}))
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
+49
-1
@@ -17,6 +17,7 @@ package kube
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -58,7 +59,12 @@ type resourceKindMap map[string][]GenericResource
|
||||
|
||||
func (rkm resourceKindMap) addResource(r GenericResource) {
|
||||
gvk := r.Resource.GroupVersionKind()
|
||||
key := gvk.Group + "/" + gvk.Kind
|
||||
var key string
|
||||
if gvk.Group != "" {
|
||||
key = gvk.Group + "/" + gvk.Kind
|
||||
} else {
|
||||
key = gvk.Kind
|
||||
}
|
||||
rkm[key] = append(rkm[key], r)
|
||||
}
|
||||
|
||||
@@ -475,3 +481,45 @@ func (resources *ResourceProvider) addResourceFromString(contents string) error
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SerializePodSpec converts a typed PodSpec into a map[string]interface{}
|
||||
func SerializePodSpec(pod *corev1.PodSpec) (map[string]interface{}, error) {
|
||||
podJSON, err := json.Marshal(pod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
podMap := make(map[string]interface{})
|
||||
err = json.Unmarshal(podJSON, &podMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return podMap, nil
|
||||
}
|
||||
|
||||
// SerializePod converts a typed Pod into a map[string]interface{}
|
||||
func SerializePod(pod *corev1.Pod) (map[string]interface{}, error) {
|
||||
podJSON, err := json.Marshal(pod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
podMap := make(map[string]interface{})
|
||||
err = json.Unmarshal(podJSON, &podMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return podMap, nil
|
||||
}
|
||||
|
||||
// SerializeContainer converts a typed Container into a map[string]interface{}
|
||||
func SerializeContainer(container *corev1.Container) (map[string]interface{}, error) {
|
||||
containerJSON, err := json.Marshal(container)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
containerMap := make(map[string]interface{})
|
||||
err = json.Unmarshal(containerJSON, &containerMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return containerMap, nil
|
||||
}
|
||||
|
||||
@@ -27,7 +27,10 @@ func ApplyAllSchemaMutations(conf *config.Configuration, resourceProvider *kube.
|
||||
if err != nil {
|
||||
return resource, err
|
||||
}
|
||||
jsonByte, err = patch.Apply(resByte)
|
||||
jsonByte, err = patch.ApplyWithOptions(resByte, &jsonpatchV5.ApplyOptions{
|
||||
AllowMissingPathOnRemove: true,
|
||||
EnsurePathExistsOnAdd: true,
|
||||
})
|
||||
if err != nil {
|
||||
return resource, err
|
||||
}
|
||||
|
||||
+37
-3
@@ -16,6 +16,7 @@ package validator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
@@ -53,12 +54,21 @@ type AuditData struct {
|
||||
Score uint
|
||||
}
|
||||
|
||||
// RemoveSuccessfulResults remove all test that have passed.
|
||||
// RemoveSuccessfulResults removes all tests that have passed
|
||||
func (res AuditData) RemoveSuccessfulResults() AuditData {
|
||||
resCopy := res
|
||||
resCopy.Results = funk.Map(res.Results, func(auditDataResult Result) Result {
|
||||
resCopy.Results = []Result{}
|
||||
|
||||
filteredResults := funk.Map(res.Results, func(auditDataResult Result) Result {
|
||||
return auditDataResult.removeSuccessfulResults()
|
||||
}).([]Result)
|
||||
|
||||
for _, result := range filteredResults {
|
||||
if result.isNotEmpty() {
|
||||
resCopy.Results = append(resCopy.Results, result)
|
||||
}
|
||||
}
|
||||
|
||||
return resCopy
|
||||
}
|
||||
|
||||
@@ -86,6 +96,10 @@ type ResultMessage struct {
|
||||
// ResultSet contiains the results for a set of checks
|
||||
type ResultSet map[string]ResultMessage
|
||||
|
||||
func (res ResultSet) isNotEmpty() bool {
|
||||
return len(res) > 0
|
||||
}
|
||||
|
||||
func (res ResultSet) removeSuccessfulResults() ResultSet {
|
||||
newResults := ResultSet{}
|
||||
for k, resultMessage := range res {
|
||||
@@ -116,6 +130,13 @@ func (res Result) removeSuccessfulResults() Result {
|
||||
return resCopy
|
||||
}
|
||||
|
||||
func (res Result) isNotEmpty() bool {
|
||||
if res.PodResult != nil {
|
||||
return res.PodResult.isNotEmpty()
|
||||
}
|
||||
return res.Results.isNotEmpty()
|
||||
}
|
||||
|
||||
// PodResult provides a list of validation messages for each pod.
|
||||
type PodResult struct {
|
||||
Name string
|
||||
@@ -132,6 +153,15 @@ func (res PodResult) removeSuccessfulResults() PodResult {
|
||||
return resCopy
|
||||
}
|
||||
|
||||
func (res PodResult) isNotEmpty() bool {
|
||||
for _, cr := range res.ContainerResults {
|
||||
if cr.isNotEmpty() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return res.Results.isNotEmpty()
|
||||
}
|
||||
|
||||
// ContainerResult provides a list of validation messages for each container.
|
||||
type ContainerResult struct {
|
||||
Name string
|
||||
@@ -144,6 +174,10 @@ func (res ContainerResult) removeSuccessfulResults() ContainerResult {
|
||||
return resCopy
|
||||
}
|
||||
|
||||
func (res ContainerResult) isNotEmpty() bool {
|
||||
return res.Results.isNotEmpty()
|
||||
}
|
||||
|
||||
func fillString(id string, l int) string {
|
||||
for len(id) < l {
|
||||
id += " "
|
||||
@@ -211,7 +245,7 @@ func (res ResultSet) GetPrettyOutput() string {
|
||||
}
|
||||
}
|
||||
if color.NoColor {
|
||||
status = status[2:] // remove emoji
|
||||
status = strings.Fields(status)[1] // remove emoji
|
||||
}
|
||||
str += fmt.Sprintf("%s%s %s\n", indent, checkColor.Sprint(fillString(msg.ID, minIDLength-len(indent))), status)
|
||||
str += fmt.Sprintf("%s %s - %s\n", indent, msg.Category, msg.Message)
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2019 FairwindsOps Inc
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package validator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
conf "github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
"github.com/fairwindsops/polaris/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
func TestGetTemplateInputReturnsPolarisSubKeys(t *testing.T) {
|
||||
pod := test.MockPod() // Includes a container, required by GetPodSpec
|
||||
pod.Spec.NodeName = "testNodeName"
|
||||
pod.ObjectMeta.Name = "testpod"
|
||||
genRes, err := kube.NewGenericResourceFromPod(pod, pod)
|
||||
require.NoError(t, err, "creating new generic resource from a pod")
|
||||
schemaTest := schemaTestCase{
|
||||
Target: conf.TargetPodSpec, // ends up being set in the case of target: PodTemplate
|
||||
Resource: genRes,
|
||||
}
|
||||
|
||||
templateInput, err := getTemplateInput(schemaTest)
|
||||
require.NoError(t, err, "getting template input from a generic resource")
|
||||
require.NotNil(t, templateInput)
|
||||
nodeName, ok, err := unstructured.NestedString(templateInput, "Polaris", "PodSpec", "nodeName")
|
||||
require.NoError(t, err, "getting Polaris.PodSpec.nodeName from template input")
|
||||
require.True(t, ok, "getting Polaris.PodSpec.nodeName from template input")
|
||||
require.Equal(t, "testNodeName", nodeName, "the nodeName from template output")
|
||||
podName, ok, err := unstructured.NestedString(templateInput, "Polaris", "PodTemplate", "metadata", "name")
|
||||
require.NoError(t, err, "getting Polaris.PodTemplate.metadata.name from template input")
|
||||
require.True(t, ok, "getting Polaris.PodTemplate.metadata.name from template input")
|
||||
require.Equal(t, "testpod", podName, "the pod from template input")
|
||||
}
|
||||
+49
-3
@@ -27,6 +27,7 @@ import (
|
||||
"gomodules.xyz/jsonpatch/v2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"github.com/fairwindsops/polaris/pkg/config"
|
||||
"github.com/fairwindsops/polaris/pkg/kube"
|
||||
@@ -64,13 +65,55 @@ func resolveCheck(conf *config.Configuration, checkID string, test schemaTestCas
|
||||
if !check.IsActionable(test.Target, test.Resource.Kind, test.IsInitContianer) {
|
||||
return nil, nil
|
||||
}
|
||||
checkPtr, err := check.TemplateForResource(test.Resource.Resource.Object)
|
||||
templateInput, err := getTemplateInput(test)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
checkPtr, err := check.TemplateForResource(templateInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return checkPtr, nil
|
||||
}
|
||||
|
||||
// getTemplateInput augments a schemaTestCase.Resource.Resource.Object with
|
||||
// Polaris built-in variables. The result can be used as input for
|
||||
// CheckSchema.TemplateForResource().
|
||||
func getTemplateInput(test schemaTestCase) (map[string]interface{}, error) {
|
||||
templateInput := test.Resource.Resource.Object
|
||||
if templateInput == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if test.Target == config.TargetPodSpec || test.Target == config.TargetContainer {
|
||||
podSpecMap, err := kube.SerializePodSpec(test.Resource.PodSpec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = unstructured.SetNestedMap(templateInput, podSpecMap, "Polaris", "PodSpec")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
podTemplateMap, ok := test.Resource.PodTemplate.(map[string]interface{})
|
||||
if ok {
|
||||
err := unstructured.SetNestedMap(templateInput, podTemplateMap, "Polaris", "PodTemplate")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if test.Target == config.TargetContainer {
|
||||
containerMap, err := kube.SerializeContainer(test.Container)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = unstructured.SetNestedMap(templateInput, containerMap, "Polaris", "Container")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return templateInput, nil
|
||||
}
|
||||
|
||||
func makeResult(conf *config.Configuration, check *config.SchemaCheck, passes bool, issues []jsonschema.ValError) ResultMessage {
|
||||
details := []string{}
|
||||
for _, issue := range issues {
|
||||
@@ -288,6 +331,9 @@ func applySchemaCheck(conf *config.Configuration, checkID string, test schemaTes
|
||||
} else if check.Target == config.TargetPodSpec {
|
||||
passes, issues, err = check.CheckPodSpec(test.Resource.PodSpec)
|
||||
prefix = getJSONSchemaPrefix(test.Resource.Kind)
|
||||
} else if check.Target == config.TargetPodTemplate {
|
||||
passes, issues, err = check.CheckPodTemplate(test.Resource.PodTemplate)
|
||||
prefix = getJSONSchemaPrefix(test.Resource.Kind)
|
||||
} else if check.Target == config.TargetContainer {
|
||||
containerIndex := funk.IndexOf(test.Resource.PodSpec.Containers, func(value corev1.Container) bool {
|
||||
return value.Name == test.Container.Name
|
||||
@@ -313,7 +359,7 @@ func applySchemaCheck(conf *config.Configuration, checkID string, test schemaTes
|
||||
namespace = test.Resource.ObjectMeta.GetName()
|
||||
}
|
||||
resources = funk.Filter(resources, func(res kube.GenericResource) bool {
|
||||
return res.ObjectMeta.GetNamespace() == namespace
|
||||
return res.ObjectMeta.GetNamespace() == "" || res.ObjectMeta.GetNamespace() == namespace
|
||||
}).([]kube.GenericResource)
|
||||
objects := funk.Map(resources, func(res kube.GenericResource) interface{} {
|
||||
return res.Resource.Object
|
||||
@@ -325,7 +371,7 @@ func applySchemaCheck(conf *config.Configuration, checkID string, test schemaTes
|
||||
}
|
||||
result := makeResult(conf, check, passes, issues)
|
||||
if !passes {
|
||||
if funk.Contains(conf.Mutations, checkID) {
|
||||
if funk.Contains(conf.Mutations, checkID) && len(check.Mutations) > 0 {
|
||||
mutations := funk.Map(check.Mutations, func(mutation jsonpatch.Operation) jsonpatch.Operation {
|
||||
mutationCopy := deepCopyMutation(mutation)
|
||||
mutationCopy.Path = prefix + mutationCopy.Path
|
||||
|
||||
@@ -44,7 +44,7 @@ func NewMutateWebhook(mgr manager.Manager, mutator Mutator) {
|
||||
}
|
||||
|
||||
func (m *Mutator) mutate(req admission.Request) ([]jsonpatch.Operation, error) {
|
||||
results, err := getValidateResults(req.AdmissionRequest.Kind.Kind, m.decoder, req, m.Config)
|
||||
results, err := GetValidatedResults(req.AdmissionRequest.Kind.Kind, m.decoder, req, m.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -55,10 +55,11 @@ func NewValidateWebhook(mgr manager.Manager, validator Validator) {
|
||||
}
|
||||
|
||||
func (v *Validator) handleInternal(req admission.Request) (*validator.Result, error) {
|
||||
return getValidateResults(req.AdmissionRequest.Kind.Kind, v.decoder, req, v.Config)
|
||||
return GetValidatedResults(req.AdmissionRequest.Kind.Kind, v.decoder, req, v.Config)
|
||||
}
|
||||
|
||||
func getValidateResults(kind string, decoder *admission.Decoder, req admission.Request, config config.Configuration) (*validator.Result, error) {
|
||||
// GetValidatedResults returns the validated results.
|
||||
func GetValidatedResults(kind string, decoder *admission.Decoder, req admission.Request, config config.Configuration) (*validator.Result, error) {
|
||||
var controller kube.GenericResource
|
||||
var err error
|
||||
if kind == "Pod" {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# This fails because automounting is true for both the pod and ServiceAccount.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
automountServiceAccountToken: true
|
||||
serviceAccountName: test
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: test
|
||||
automountServiceAccountToken: true
|
||||
@@ -0,0 +1,19 @@
|
||||
# This fails because automounting is true for the pod, overriding the ServiceAccount.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
automountServiceAccountToken: true
|
||||
serviceAccountName: test
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: test
|
||||
automountServiceAccountToken: false
|
||||
@@ -0,0 +1,17 @@
|
||||
# This fails because automounting is not disabled anywhere.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
serviceAccountName: test
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: test
|
||||
@@ -0,0 +1,19 @@
|
||||
# This succeeds because automounting is disabled at the pod.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
serviceAccountName: test
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: test
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# This succeeds because automounting is disabled at the pod and ServiceAccount.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
serviceAccountName: test
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: test
|
||||
automountServiceAccountToken: false
|
||||
@@ -0,0 +1,19 @@
|
||||
# This succeeds because automounting is disabled at the pod, overriding the ServiceAccount.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
serviceAccountName: test
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: test
|
||||
automountServiceAccountToken: true
|
||||
@@ -0,0 +1,18 @@
|
||||
# This succeeds because automounting is disabled at the ServiceAccount.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
serviceAccountName: test
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: test
|
||||
automountServiceAccountToken: false
|
||||
@@ -10,5 +10,5 @@ spec:
|
||||
name: nginx
|
||||
resources:
|
||||
limits:
|
||||
cpu: 100m #TODO: Set this to the amount of CPU you want to reserve for your workload
|
||||
cpu: 100m #TODO: Set this to the maximum amount of CPU you want your workload to use
|
||||
memory: 128Mi
|
||||
|
||||
@@ -6,9 +6,9 @@ metadata:
|
||||
app.kubernetes.io/name: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
- name: nginx
|
||||
image: nginx
|
||||
securityContext:
|
||||
capabilities:
|
||||
add:
|
||||
- NET_ADMIN
|
||||
- NET_ADMIN
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: nginx
|
||||
name: nginx
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx
|
||||
name: nginx
|
||||
resources: {}
|
||||
securityContext: {}
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
labels:
|
||||
env: test
|
||||
name: nginx
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx
|
||||
name: nginx
|
||||
resources: {}
|
||||
securityContext:
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
labels:
|
||||
env: test
|
||||
name: nginx
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx
|
||||
name: nginx
|
||||
resources: {}
|
||||
securityContext:
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
@@ -0,0 +1,18 @@
|
||||
# This fails because the annotation names do not match the containers.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
annotations:
|
||||
container.apparmor.security.beta.kubernetes.io/container1: runtime/default
|
||||
container.apparmor.security.beta.kubernetes.io/container2: runtime/default
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
- name: nginx2
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 81
|
||||
@@ -0,0 +1,17 @@
|
||||
# This fails because ALL capabilities are also added.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
securityContext:
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- all
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -0,0 +1,11 @@
|
||||
# This fails because none of the seccompProfile, seLinuxOptions, AppArmor, or dropping capabilities are present.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -0,0 +1,17 @@
|
||||
# This fails because the container overrides the pod profile type.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
securityContext:
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: nginx
|
||||
securityContext:
|
||||
seccompProfile:
|
||||
type: Unconfined
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -0,0 +1,13 @@
|
||||
# This fails because the pod seccompPRofile is missing the type field.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
securityContext:
|
||||
seccompProfile:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -0,0 +1,14 @@
|
||||
# This fails because the container overrides the pod profile type.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
securityContext:
|
||||
seccompProfile:
|
||||
type: Unconfined
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -0,0 +1,13 @@
|
||||
# This fails because seLinuxOptions is defined but without any sub-field.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
securityContext:
|
||||
seLinuxOptions:
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -0,0 +1,13 @@
|
||||
# This fails because seLinuxOptions is defined but without any sub-field.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
securityContext:
|
||||
seLinuxOptions:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -0,0 +1,18 @@
|
||||
# This succeeds because AppArmor annotations exist matching both container names.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
annotations:
|
||||
container.apparmor.security.beta.kubernetes.io/nginx: runtime/default
|
||||
container.apparmor.security.beta.kubernetes.io/nginx2: runtime/default
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
- name: nginx2
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 81
|
||||
@@ -0,0 +1,17 @@
|
||||
# This succeeds because SOME capability is dropped, and ALL capabilities are not added.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
securityContext:
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- someCapabilities
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -0,0 +1,14 @@
|
||||
# This succeeds because a seccomp profile is defined for the container.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
securityContext:
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -0,0 +1,14 @@
|
||||
# This succeeds because a seccomp profile is defined for the pod and not undefined for the container.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
securityContext:
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
# This succeeds because the container seccomp profile overrides the pod Unconfined setting.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
securityContext:
|
||||
seccompProfile:
|
||||
type: Unconfined
|
||||
containers:
|
||||
- name: nginx
|
||||
securityContext:
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -0,0 +1,14 @@
|
||||
# This succeeds because seLinuxOptions are defined for the container.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
securityContext:
|
||||
seLinuxOptions:
|
||||
level: "s0:c123,c456"
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -0,0 +1,14 @@
|
||||
# This succeeds because seLinuxOptions is defined for the pod.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
securityContext:
|
||||
seLinuxOptions:
|
||||
level: "s0:c123,c456"
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -0,0 +1,13 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx
|
||||
labels:
|
||||
app.kubernetes.io/name: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
resources:
|
||||
limits:
|
||||
cpu: 100m
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: nginx
|
||||
name: nginx
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx
|
||||
name: nginx
|
||||
resources:
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 512Mi #TODO: Set this to the maximum amount of memory you want your workload to use
|
||||
@@ -10,4 +10,4 @@ spec:
|
||||
name: nginx
|
||||
resources:
|
||||
limits:
|
||||
memory: 512Mi #TODO: Set this to the amount of Memory you want to reserve for your workload
|
||||
memory: 512Mi #TODO: Set this to the maximum amount of memory you want your workload to use
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# This failes because the NetworkPolicy `policyTypes` lacks `Egress`,
|
||||
# without which the egress rules will not be applied.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
labels:
|
||||
security: medium
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
security: medium
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
- from:
|
||||
- ipBlock:
|
||||
cidr: 0.0.0.0/0
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
egress:
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
# Allow outbound with other medium-security pods.
|
||||
security: medium
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
@@ -0,0 +1,32 @@
|
||||
# This fails because the NetworkPolicy lacks egress rules.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
labels:
|
||||
security: medium
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
security: medium
|
||||
policyTypes:
|
||||
- Egress
|
||||
- Ingress
|
||||
ingress:
|
||||
- from:
|
||||
- ipBlock:
|
||||
cidr: 0.0.0.0/0
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
@@ -0,0 +1,41 @@
|
||||
# This failes because the NetworkPolicy `policyTypes` lacks `Ingress`,
|
||||
# without which the egress rules will not be applied.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
labels:
|
||||
security: medium
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
security: medium
|
||||
policyTypes:
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- ipBlock:
|
||||
cidr: 0.0.0.0/0
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
egress:
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
# Allow outbound with other medium-security pods.
|
||||
security: medium
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
@@ -0,0 +1,34 @@
|
||||
# This fails because the NetworkPolicy lacks ingress rules.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
labels:
|
||||
security: medium
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
security: medium
|
||||
policyTypes:
|
||||
- Egress
|
||||
- Ingress
|
||||
egress:
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
# Allow outbound with other medium-security pods.
|
||||
security: medium
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
@@ -0,0 +1,41 @@
|
||||
# This fails because the NetworkPolicy matches a label the pod does not have
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
labels:
|
||||
security: medium
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
security: high
|
||||
policyTypes:
|
||||
- Egress
|
||||
- Ingress
|
||||
ingress:
|
||||
- from:
|
||||
- ipBlock:
|
||||
cidr: 0.0.0.0/0
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
egress:
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
# Allow outbound with other medium-security pods.
|
||||
security: medium
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
@@ -0,0 +1,41 @@
|
||||
# This succeeds because the NetworkPolicy contains and enables egress and ingress rules, and targets the pod's label.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
labels:
|
||||
security: medium
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
security: medium
|
||||
policyTypes:
|
||||
- Egress
|
||||
- Ingress
|
||||
ingress:
|
||||
- from:
|
||||
- ipBlock:
|
||||
cidr: 0.0.0.0/0
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
egress:
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
# Allow outbound with other medium-security pods.
|
||||
security: medium
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
@@ -0,0 +1,7 @@
|
||||
# This fails because the key AWS_SECRET_ACCESS_KEY is set.
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: test-config
|
||||
data:
|
||||
AWS_SECRET_ACCESS_KEY: xxxxx
|
||||
@@ -0,0 +1,7 @@
|
||||
# This fails because the key AZURE_BATCH_KEY is set.
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: test-config
|
||||
data:
|
||||
AZURE_BATCH_KEY: xxxxx
|
||||
@@ -0,0 +1,7 @@
|
||||
# This fails because the key OCI_CLI_KEY_CONTENT is set.
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: test-config
|
||||
data:
|
||||
OCI_CLI_KEY_CONTENT: xxxxx
|
||||
@@ -0,0 +1,8 @@
|
||||
# This fails because a key contains "password".
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: test-config
|
||||
data:
|
||||
db_username: postgres
|
||||
db_password: abc123
|
||||
@@ -0,0 +1,11 @@
|
||||
# This fails because a value contains a private key.
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: test-config
|
||||
data:
|
||||
a_key: and value that are ok
|
||||
ssh_access: |
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
xxxxxxxx
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
@@ -0,0 +1,6 @@
|
||||
# This succeeds because no data nor keys nor values are defined.
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: test-config
|
||||
#data:
|
||||
@@ -0,0 +1,8 @@
|
||||
# This succeeds because no keys or values are invalid.
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: test-config
|
||||
data:
|
||||
message: This is a test
|
||||
db_user: postgres
|
||||
@@ -0,0 +1,16 @@
|
||||
# This fails because an environment variable name AWS_SECRET_ACCESS_KEY is set.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
env:
|
||||
- name: password
|
||||
value: abc123
|
||||
- name: AWS_SECRET_ACCESS_KEY
|
||||
value: xxx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -0,0 +1,16 @@
|
||||
# This fails because an environment variable named AZURE_BATCH_KEY is set.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
env:
|
||||
- name: password
|
||||
value: abc123
|
||||
- name: AZURE_BATCH_KEY
|
||||
value: xxx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -0,0 +1,16 @@
|
||||
# This fails because an environment variable named OCI_CLI_KEY_CONTENT is set.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
env:
|
||||
- name: password
|
||||
value: abc123
|
||||
- name: OCI_CLI_KEY_CONTENT
|
||||
value: xxx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -0,0 +1,16 @@
|
||||
# This fails because an environment variable name contains "password".
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
env:
|
||||
- name: user_name
|
||||
value: postgres
|
||||
- name: my_password
|
||||
value: abc123
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -0,0 +1,19 @@
|
||||
# This fails because an environment variable value contains a private key.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
env:
|
||||
- name: user_name
|
||||
value: postgres
|
||||
- name: the_good_stuff
|
||||
value: |
|
||||
---BEGIN OPENSSH PRIVATE KEY---
|
||||
xxxxx
|
||||
---END OPENSSH PRIVATE KEY---
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -0,0 +1,11 @@
|
||||
# This succeeds because there are no environment variables.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -0,0 +1,14 @@
|
||||
# This succeeds because there are no sensitive environment variable names or values.
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test-pod
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
env:
|
||||
- name: greeting
|
||||
value: hello
|
||||
image: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -199,6 +199,8 @@ func SetupTestAPI(objects ...runtime.Object) (kubernetes.Interface, dynamic.Inte
|
||||
APIResources: []metav1.APIResource{
|
||||
{Name: "pods", Namespaced: true, Kind: "Pod"},
|
||||
{Name: "replicationcontrollers", Namespaced: true, Kind: "ReplicationController"},
|
||||
{Name: "serviceaccounts", Namespaced: true, Kind: "ServiceAccount"},
|
||||
{Name: "configmaps", Namespaced: true, Kind: "ConfigMap"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -239,6 +241,7 @@ func SetupTestAPI(objects ...runtime.Object) (kubernetes.Interface, dynamic.Inte
|
||||
GroupVersion: "networking.k8s.io/v1",
|
||||
APIResources: []metav1.APIResource{
|
||||
{Name: "ingresses", Namespaced: true, Kind: "Ingress", Version: "v1"},
|
||||
{Name: "networkpolicies", Namespaced: true, Kind: "NetworkPolicy", Version: "v1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -33,14 +33,18 @@ func TestMutations(t *testing.T) {
|
||||
c, err := config.Parse([]byte(configYaml))
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, c.Mutations, 0)
|
||||
mutations := []string{"hostIPCSet", "pullPolicyNotAlways", "hostPIDSet", "hostNetworkSet", "deploymentMissingReplicas", "runAsRootAllowed", "cpuRequestsMissing", "cpuLimitsMissing", "memoryRequestsMissing", "memoryLimitsMissing", "livenessProbeMissing", "readinessProbeMissing"}
|
||||
for _, mutationStr := range mutations {
|
||||
for _, tc := range failureTestCasesMap[mutationStr] {
|
||||
for mutationStr := range mutationTestCasesMap {
|
||||
if len(mutationTestCasesMap[mutationStr]) == 0 {
|
||||
panic("No test cases found for " + mutationStr)
|
||||
}
|
||||
for _, tc := range mutationTestCasesMap[mutationStr] {
|
||||
newConfig := c
|
||||
key := fmt.Sprintf("%s/%s", tc.check, strings.ReplaceAll(tc.filename, "failure", "mutated"))
|
||||
mutatedYamlContent, ok := mutatedYamlContentMap[key]
|
||||
assert.True(t, ok)
|
||||
assert.Len(t, tc.resources.Resources, 1)
|
||||
newConfig.Checks = map[string]config.Severity{}
|
||||
newConfig.Checks[mutationStr] = config.SeverityDanger
|
||||
newConfig.Mutations = []string{mutationStr}
|
||||
results, err := validator.ApplyAllSchemaChecksToResourceProvider(&newConfig, tc.resources)
|
||||
assert.NoError(t, err)
|
||||
@@ -56,7 +60,7 @@ func TestMutations(t *testing.T) {
|
||||
yamlContent, err := yaml.JSONToYAML(mutated.OriginalObjectJSON)
|
||||
assert.NoError(t, err)
|
||||
contentStr := mutation.UpdateMutatedContentWithComments(string(yamlContent), comments)
|
||||
assert.EqualValues(t, mutatedYamlContent, contentStr)
|
||||
assert.EqualValues(t, mutatedYamlContent, contentStr, "Mutation test case for " + tc.check + "/" + tc.filename + " failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+21
-18
@@ -41,9 +41,10 @@ type testCase struct {
|
||||
}
|
||||
|
||||
var mutatedYamlContentMap = map[string]string{}
|
||||
var failureTestCasesMap = map[string][]testCase{}
|
||||
var mutationTestCasesMap = map[string][]testCase{}
|
||||
|
||||
func init() {
|
||||
checkToTest := os.Getenv("POLARIS_CHECK_TEST")
|
||||
_, baseDir, _, _ := runtime.Caller(0)
|
||||
baseDir = filepath.Dir(baseDir) + "/checks"
|
||||
dirs, err := ioutil.ReadDir(baseDir)
|
||||
@@ -52,6 +53,9 @@ func init() {
|
||||
}
|
||||
for _, dir := range dirs {
|
||||
check := dir.Name()
|
||||
if checkToTest != "" && checkToTest != check {
|
||||
continue
|
||||
}
|
||||
checkDir := baseDir + "/" + check
|
||||
cases, err := ioutil.ReadDir(checkDir)
|
||||
if err != nil {
|
||||
@@ -76,10 +80,19 @@ func init() {
|
||||
if tc.Name() == "check.yaml" {
|
||||
continue
|
||||
}
|
||||
resources, err := kube.CreateResourceProviderFromPath(checkDir + "/" + tc.Name())
|
||||
resourceFilename := strings.Replace(tc.Name(), "mutated", "failure", -1)
|
||||
|
||||
resources, err := kube.CreateResourceProviderFromPath(checkDir + "/" + resourceFilename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
testcase := testCase{
|
||||
filename: tc.Name(),
|
||||
check: check,
|
||||
resources: resources,
|
||||
failure: strings.Contains(resourceFilename, "failure"),
|
||||
config: c,
|
||||
}
|
||||
|
||||
if strings.Contains(tc.Name(), "mutated") {
|
||||
yamlContent, err := os.ReadFile(checkDir + "/" + tc.Name())
|
||||
@@ -88,24 +101,14 @@ func init() {
|
||||
}
|
||||
key := fmt.Sprintf("%s/%s", check, tc.Name())
|
||||
mutatedYamlContentMap[key] = string(yamlContent)
|
||||
} else {
|
||||
testcase := testCase{
|
||||
filename: tc.Name(),
|
||||
check: check,
|
||||
resources: resources,
|
||||
failure: strings.Contains(tc.Name(), "failure"),
|
||||
config: c,
|
||||
testCases, ok := mutationTestCasesMap[check]
|
||||
if !ok {
|
||||
testCases = []testCase{}
|
||||
}
|
||||
testCases = append(testCases, testcase)
|
||||
|
||||
if strings.Contains(tc.Name(), "mutated") {
|
||||
testCases, ok := failureTestCasesMap[check]
|
||||
if !ok {
|
||||
testCases = []testCase{}
|
||||
}
|
||||
testCases = append(testCases, testcase)
|
||||
failureTestCasesMap[check] = testCases
|
||||
}
|
||||
mutationTestCasesMap[check] = testCases
|
||||
} else {
|
||||
testCases = append(testCases, testcase)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-11
@@ -1,8 +1,8 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Testing to ensure that the webhook starts up, allows a correct deployment to pass,
|
||||
# and prevents a incorrectly formatted deployment.
|
||||
echo "Testing to ensure that the webhook starts up, allows a correct deployment to pass, and prevents a incorrectly formatted deployment."
|
||||
|
||||
BLUE='\033[0;34m'
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
@@ -57,9 +57,11 @@ function clean_up() {
|
||||
# || true to avoid issues when we cannot delete
|
||||
kubectl delete -f $filename ||true
|
||||
done
|
||||
# Uninstall webhook and webhook config
|
||||
kubectl delete validatingwebhookconfigurations polaris-webhook --wait=false
|
||||
kubectl -n polaris delete deploy -l app=polaris --wait=false
|
||||
echo "Uninstalling webhook and webhook config"
|
||||
kubectl delete validatingwebhookconfigurations polaris-webhook --wait=false || true
|
||||
kubectl delete validatingwebhookconfigurations polaris-validate-webhook --wait=false || true
|
||||
kubectl delete validatingwebhookconfigurations polaris-mutate-webhook --wait=false || true
|
||||
kubectl -n polaris delete deploy -l app=polaris --wait=false || true
|
||||
echo -e "\n\nDone cleaning up\n\n"
|
||||
}
|
||||
|
||||
@@ -77,26 +79,26 @@ kubectl create ns scale-test
|
||||
kubectl create ns polaris
|
||||
kubectl create ns tests
|
||||
|
||||
# Install a bad deployment
|
||||
echo "Installing a bad deployment"
|
||||
kubectl apply -n scale-test -f ./test/webhook_cases/failing_test.deployment.yaml
|
||||
|
||||
# Install the webhook
|
||||
echo "Installing the webhook"
|
||||
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
|
||||
helm install polaris fairwinds-stable/polaris --namespace polaris --create-namespace \
|
||||
--set dashboard.enable=false \
|
||||
--set webhook.enable=true \
|
||||
--set image.tag=$CI_SHA1
|
||||
|
||||
# wait for the webhook to come online
|
||||
echo "Waiting for the webhook to come online"
|
||||
check_webhook_is_ready
|
||||
sleep 5
|
||||
|
||||
kubectl logs -n polaris $(kubectl get po -oname -n polaris | grep webhook) --follow &
|
||||
|
||||
# Webhook started, setting all tests as passed initially.
|
||||
echo "Webhook started"
|
||||
ALL_TESTS_PASSED=1
|
||||
|
||||
# Run tests against correctly configured objects
|
||||
echo "Running tests against correctly configured objects"
|
||||
for filename in test/webhook_cases/passing_test.*.yaml; do
|
||||
echo -e "\n\n"
|
||||
echo -e "${BLUE}TEST CASE: $filename${NC}"
|
||||
@@ -109,7 +111,7 @@ for filename in test/webhook_cases/passing_test.*.yaml; do
|
||||
kubectl delete -n tests -f $filename || true
|
||||
done
|
||||
|
||||
# Run tests against incorrectly configured objects
|
||||
echo "Running tests against incorrectly configured objects"
|
||||
for filename in test/webhook_cases/failing_test.*.yaml; do
|
||||
echo -e "\n\n"
|
||||
echo -e "${BLUE}TEST CASE: $filename${NC}"
|
||||
@@ -123,6 +125,7 @@ for filename in test/webhook_cases/failing_test.*.yaml; do
|
||||
kubectl delete -n tests -f $filename || true
|
||||
done
|
||||
|
||||
echo "Checking ability to scale"
|
||||
kubectl -n scale-test scale deployment nginx-deployment --replicas=2
|
||||
sleep 5
|
||||
kubectl get po -n scale-test
|
||||
@@ -133,9 +136,12 @@ if [ $pod_count != 2 ]; then
|
||||
fi
|
||||
|
||||
if [ -z $SKIP_FINAL_CLEANUP ]; then
|
||||
echo "Doing final cleanup..."
|
||||
clean_up
|
||||
fi
|
||||
|
||||
echo "Done with tests"
|
||||
|
||||
#Verify that all the tests passed.
|
||||
if [ $ALL_TESTS_PASSED -eq 1 ]; then
|
||||
echo "Tests Passed."
|
||||
|
||||
Reference in New Issue
Block a user