Merge pull request #8 from digitalocean/varsha/namespace-check

Namespace check: Check if there are any k8s objects in the default namespace
varsha/versions
Varsha Varadarajan 2019-06-14 14:11:08 -04:00 committed by GitHub
commit 8d1893c06d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 269 additions and 0 deletions

View File

@ -3,5 +3,6 @@
package all
import (
_ "github.com/digitalocean/clusterlint/checks/basic"
_ "github.com/digitalocean/clusterlint/checks/noop"
)

199
checks/basic/namespace.go Normal file
View File

@ -0,0 +1,199 @@
package basic
import (
"fmt"
"strings"
"sync"
"github.com/digitalocean/clusterlint/checks"
"github.com/digitalocean/clusterlint/kube"
"golang.org/x/sync/errgroup"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const namespace = "default"
func init() {
checks.Register(&defaultNamespaceCheck{})
}
type defaultNamespaceCheck struct{}
type alert struct {
warnings []error
mu sync.Mutex
}
// GetWarnings returns alert.warnings
func (alert *alert) GetWarnings() []error {
return alert.warnings
}
// SetWarnings sets alert.warnings
func (alert *alert) SetWarnings(w []error) {
alert.warnings = w
}
// AppendWarning appends a warning to the warnings slice
func (alert *alert) AppendWarning(err error) {
alert.warnings = append(alert.warnings, err)
}
// warn adds warnings for k8s objects that should not be in the default namespace
func (alert *alert) warn(k8stype string, item metav1.ObjectMeta) {
alert.mu.Lock()
alert.AppendWarning(fmt.Errorf("%s '%s' is in the default namespace.", k8stype, item.GetName()))
alert.mu.Unlock()
}
// Name returns a unique name for this check.
func (nc *defaultNamespaceCheck) Name() string {
return "default-namespace"
}
// Groups returns a list of group names this check should be part of.
func (nc *defaultNamespaceCheck) Groups() []string {
return []string{"basic"}
}
// Description returns a detailed human-readable description of what this check
// does.
func (nc *defaultNamespaceCheck) Description() string {
return "Checks if there are any user created k8s objects in the default namespace."
}
// checkPods checks if there are pods in the default namespace
func checkPods(items *corev1.PodList, alert *alert) {
for _, item := range items.Items {
if namespace == item.GetNamespace() {
alert.warn("Pod", item.ObjectMeta)
}
}
}
// checkPodTemplates checks if there are pod templates in the default namespace
func checkPodTemplates(items *corev1.PodTemplateList, alert *alert) {
for _, item := range items.Items {
if namespace == item.GetNamespace() {
alert.warn("Pod template", item.ObjectMeta)
}
}
}
// checkPVCs checks if there are pvcs in the default namespace
func checkPVCs(items *corev1.PersistentVolumeClaimList, alert *alert) {
for _, item := range items.Items {
if namespace == item.GetNamespace() {
alert.warn("Persistent Volume Claim", item.ObjectMeta)
}
}
}
// checkConfigMaps checks if there are config maps in the default namespace
func checkConfigMaps(items *corev1.ConfigMapList, alert *alert) {
for _, item := range items.Items {
if namespace == item.GetNamespace() {
alert.warn("Config Map", item.ObjectMeta)
}
}
}
// checkQuotas checks if there are quotas in the default namespace
func checkQuotas(items *corev1.ResourceQuotaList, alert *alert) {
for _, item := range items.Items {
if namespace == item.GetNamespace() {
alert.warn("Resource Quota", item.ObjectMeta)
}
}
}
// checkLimits checks if there are limits in the default namespace
func checkLimits(items *corev1.LimitRangeList, alert *alert) {
for _, item := range items.Items {
if namespace == item.GetNamespace() {
alert.warn("Limit Range", item.ObjectMeta)
}
}
}
// checkServices checks if there are user created services in the default namespace
func checkServices(items *corev1.ServiceList, alert *alert) {
for _, item := range items.Items {
if namespace == item.GetNamespace() && item.GetName() != "kubernetes" {
alert.warn("Service", item.ObjectMeta)
}
}
}
// checkSecrets checks if there are user created secrets in the default namespace
func checkSecrets(items *corev1.SecretList, alert *alert) {
for _, item := range items.Items {
if namespace == item.GetNamespace() && !strings.Contains(item.GetName(), "default-token-") {
alert.warn("Secret", item.ObjectMeta)
}
}
}
// checkSA checks if there are user created SAs in the default namespace
func checkSA(items *corev1.ServiceAccountList, alert *alert) {
for _, item := range items.Items {
if namespace == item.GetNamespace() && item.GetName() != "default" {
alert.warn("Service Account", item.ObjectMeta)
}
}
}
// Run runs this check on a set of Kubernetes objects. It can return warnings
// (low-priority problems) and errors (high-priority problems) as well as an
// error value indicating that the check failed to run.
func (nc *defaultNamespaceCheck) Run(objects *kube.Objects) (warnings []error, errors []error, err error) {
alert := &alert{}
var g errgroup.Group
g.Go(func() error {
checkPods(objects.Pods, alert)
return nil
})
g.Go(func() error {
checkPodTemplates(objects.PodTemplates, alert)
return nil
})
g.Go(func() error {
checkPVCs(objects.PersistentVolumeClaims, alert)
return nil
})
g.Go(func() error {
checkConfigMaps(objects.ConfigMaps, alert)
return nil
})
g.Go(func() error {
checkQuotas(objects.ResourceQuotas, alert)
return nil
})
g.Go(func() error {
checkLimits(objects.LimitRanges, alert)
return nil
})
g.Go(func() error {
checkServices(objects.Services, alert)
return nil
})
g.Go(func() error {
checkSecrets(objects.Secrets, alert)
return nil
})
g.Go(func() error {
checkSA(objects.ServiceAccounts, alert)
return nil
})
return alert.warnings, nil, g.Wait()
}

View File

@ -0,0 +1,69 @@
package basic
import (
"fmt"
"testing"
"github.com/digitalocean/clusterlint/kube"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestNamespaceWarning(t *testing.T) {
scenarios := []struct {
name string
arg *kube.Objects
expected []error
}{
{"no objects in cluster", empty(), nil},
{"user created objects in default namespace", userCreatedObjects(), errors()},
}
namespace := defaultNamespaceCheck{}
for _, scenario := range scenarios {
t.Run(scenario.name, func(t *testing.T) {
w, _, _ := namespace.Run(scenario.arg)
assert.ElementsMatch(t, scenario.expected, w)
})
}
}
func empty() *kube.Objects {
objs := &kube.Objects{
Pods: &corev1.PodList{},
PodTemplates: &corev1.PodTemplateList{},
PersistentVolumeClaims: &corev1.PersistentVolumeClaimList{},
ConfigMaps: &corev1.ConfigMapList{},
Services: &corev1.ServiceList{},
Secrets: &corev1.SecretList{},
ServiceAccounts: &corev1.ServiceAccountList{},
ResourceQuotas: &corev1.ResourceQuotaList{},
LimitRanges: &corev1.LimitRangeList{},
}
return objs
}
func userCreatedObjects() *kube.Objects {
objs := empty()
objs.Pods = &corev1.PodList{Items: []corev1.Pod{{ObjectMeta: metav1.ObjectMeta{Name: "pod_foo", Namespace: "default"}}}}
objs.PodTemplates = &corev1.PodTemplateList{Items: []corev1.PodTemplate{{ObjectMeta: metav1.ObjectMeta{Name: "template_foo", Namespace: "default"}}}}
objs.PersistentVolumeClaims = &corev1.PersistentVolumeClaimList{Items: []corev1.PersistentVolumeClaim{{ObjectMeta: metav1.ObjectMeta{Name: "pvc_foo", Namespace: "default"}}}}
objs.ConfigMaps = &corev1.ConfigMapList{Items: []corev1.ConfigMap{{ObjectMeta: metav1.ObjectMeta{Name: "cm_foo", Namespace: "default"}}}}
objs.ResourceQuotas = &corev1.ResourceQuotaList{Items: []corev1.ResourceQuota{{ObjectMeta: metav1.ObjectMeta{Name: "quota_foo", Namespace: "default"}}}}
objs.LimitRanges = &corev1.LimitRangeList{Items: []corev1.LimitRange{{ObjectMeta: metav1.ObjectMeta{Name: "limit_foo", Namespace: "default"}}}}
return objs
}
func errors() []error {
w := []error{
fmt.Errorf("Pod 'pod_foo' is in the default namespace."),
fmt.Errorf("Pod template 'template_foo' is in the default namespace."),
fmt.Errorf("Persistent Volume Claim 'pvc_foo' is in the default namespace."),
fmt.Errorf("Config Map 'cm_foo' is in the default namespace."),
fmt.Errorf("Resource Quota 'quota_foo' is in the default namespace."),
fmt.Errorf("Limit Range 'limit_foo' is in the default namespace."),
}
return w
}