Compare commits

..
7 Commits
Author SHA1 Message Date
Andrew Suderman 166b39b695 move to latest alpine (#944) 2023-05-16 16:24:13 -06:00
StevieandRobert Brennan ef4ea66d49 Update checks documentation (#936)
* Update docs with new checks; make sure both config-full has all config.yml checks

* Update doc on mutating webhooks

---------

Co-authored-by: Robert Brennan <accounts@rbren.io>
2023-05-16 16:31:43 -04:00
Terraform User b472f34266 Managed by Terraform 2023-04-06 06:54:22 -06:00
Robert Brennan 1ddd2d985a Update documentation from template (#935) 2023-03-31 10:43:46 -04:00
Robert Brennan 727c6fa2c3 Update documentation from template (#934) 2023-03-31 10:24:11 -04:00
Robert Brennan f531103fa0 Update documentation from template (#926) 2023-03-14 11:25:31 -06:00
Pål-Magnus Slåtto d909dfd00b chore(ci): Upgrade Github CI/CD to non-deprecated actions (#933) 2023-03-13 10:29:02 -04:00
17 changed files with 2024 additions and 1874 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# The action uses an own Dockerfile on purpose because the root Dockerfile takes way too long to build for an action
FROM alpine:3.10
FROM alpine:3.17
RUN apk add --no-cache \
bash \
+1 -1
View File
@@ -17,4 +17,4 @@ mkdir polaris
tar -xzf $TARGET_FILE -C polaris
rm $TARGET_FILE
echo "polaris" >> $GITHUB_PATH
echo "::set-output name=version::$INPUT_VERSION"
echo "version=$INPUT_VERSION" >> $GITHUB_OUTPUT
+2 -2
View File
@@ -7,7 +7,7 @@ jobs:
build-int:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup polaris
uses: ./.github/actions/setup-polaris
with:
@@ -18,7 +18,7 @@ jobs:
build-ext:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup polaris
uses: fairwindsops/polaris/.github/actions/setup-polaris@master
with:
+1 -1
View File
@@ -1,4 +1,4 @@
FROM alpine:20230208
FROM alpine:3.18
LABEL org.opencontainers.image.authors="FairwindsOps, Inc." \
org.opencontainers.image.vendor="FairwindsOps, Inc." \
+3 -2
View File
@@ -38,8 +38,9 @@ and network with fellow Kubernetes users.
or
[join the user group](https://www.fairwinds.com/open-source-software-user-group) to get involved!
<a href="https://www.fairwinds.com/t-shirt-offer?utm_source=polaris&utm_medium=polaris&utm_campaign=polaris-tshirt">
<img src="https://www.fairwinds.com/hubfs/Doc_Banners/Fairwinds_OSS_User_Group_740x125_v6.png" alt="Love Fairwinds Open Source? Share your business email and job title and we'll send you a free Fairwinds t-shirt!" />
<a href="https://insights.fairwinds.com/auth/register/">
<img src="https://www.fairwinds.com/hubfs/Doc_Banners/Fairwinds_OSS_User_Group_740x125_v6.png"
alt="Love Fairwinds Open Source? Automate Fairwinds Open Source for free with Fairwinds Insights. Click to learn more" />
</a>
## Other Projects from Fairwinds
+13 -12
View File
@@ -113,19 +113,20 @@ var fixCommand = &cobra.Command{
updatedYamlContent := ""
if len(allMutations) > 0 {
for _, resource := range kubeResources.Resources {
key := fmt.Sprintf("%s/%s/%s", resource.Kind, resource.Resource.GetName(), resource.Resource.GetNamespace())
fmt.Println("resource", key)
mutations := allMutations[key]
mutatedYamlContent, err := mutation.ApplyAllMutations(string(resource.OriginalObjectYAML), mutations)
if err != nil {
logrus.Errorf("Error applying schema mutations to the resource %s: %v", key, err)
os.Exit(1)
for _, resources := range kubeResources.Resources {
for _, resource := range resources {
key := fmt.Sprintf("%s/%s/%s", resource.Kind, resource.Resource.GetName(), resource.Resource.GetNamespace())
mutations := allMutations[key]
mutatedYamlContent, err := mutation.ApplyAllMutations(string(resource.OriginalObjectYAML), mutations)
if err != nil {
logrus.Errorf("Error applying schema mutations to the resource %s: %v", key, err)
os.Exit(1)
}
if updatedYamlContent != "" {
updatedYamlContent += "\n---\n"
}
updatedYamlContent += mutatedYamlContent
}
if updatedYamlContent != "" {
updatedYamlContent += "\n---\n"
}
updatedYamlContent += mutatedYamlContent
}
}
+22 -1
View File
@@ -55,7 +55,28 @@ 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
The following default checks currently have mutation support enabled:
* `hostPIDSet`
* `hostNetworkSet`
* `hostIPCSet`
* `priorityClassNotSet`
* `hostPortSet`
* `pullPolicyNotAlways`
* `deploymentMissingReplicas`
* `dangerousCapabilities`
* `cpuLimitsMissing`
* `memoryLimitsMissing`
* `livenessProbeMissing`
* `memoryRequestsMissing`
* `cpuRequestsMissing`
* `runAsPrivileged`
* `readinessProbeMissing`
* `privilegeEscalationAllowed`
* `notReadOnlyRootFilesystem`
* `insecureCapabilities`
* `runAsRootAllowed`
If you'd like to
enable other mutations, you can set the `webhook.mutations` flag.
+2 -1
View File
@@ -16,7 +16,8 @@ key | default | description
`pullPolicyNotAlways` | `warning` | Fails when an image pull policy is not `always`.
`priorityClassNotSet` | `ignore` | Fails when a priorityClassName is not set for a pod.
`deploymentMissingReplicas` | `warning` | Fails when there is only one replica for a deployment.
`missingPodDisruptionBudget` | `ignore`
`missingPodDisruptionBudget` | `ignore`
`metadataAndNameMismatched` | `ignore`
`topologySpreadConstraint` | `warning` | Fails when there is no topology spread constraint on the pod
## Background
+13
View File
@@ -11,8 +11,10 @@ for privilege escalation.
key | default | description
----|---------|------------
`automountServiceAccountToken` | `warning` | Fails when `automountServiceAccountToken` is automounted.
`hostIPCSet` | `danger` | Fails when `hostIPC` attribute is configured.
`hostPIDSet` | `danger` | Fails when `hostPID` attribute is configured.
`linuxHardening` | `danger` | Fails when neither `AppArmor`, `Seccomp`, `SELinux`, or dropping Linux Capabilities is in use.
`notReadOnlyRootFilesystem` | `warning` | Fails when `securityContext.readOnlyRootFilesystem` is not true.
`privilegeEscalationAllowed` | `danger` | Fails when `securityContext.allowPrivilegeEscalation` is true.
`runAsRootAllowed` | `warning` | Fails when `securityContext.runAsNonRoot` is not true.
@@ -22,6 +24,17 @@ key | default | description
`hostNetworkSet` | `warning` | Fails when `hostNetwork` attribute is configured.
`hostPortSet` | `warning` | Fails when `hostPort` attribute is configured.
`tlsSettingsMissing` | `warning` | Fails when an Ingress lacks TLS settings.
`sensitiveContainerEnvVar` | `warning` | Fails when the container sets potentially sensitive environment variables.
`sensitiveConfigmapContent` | `warning` | Fails when potentially sensitive content is detected in the ConfigMap keys or values.
`missingNetworkPolicy` | `ignore`
`clusterrolePodExecAttach` | `warning` | Fails when the ClusterRole allows Pods/exec or pods/attach.
`rolePodExecAttach` | `warning` | Fails when the Role allows Pods/exec or pods/attach.
`clusterrolebindingPodExecAttach` | `warning` | Fails when the ClusterRoleBinding references a ClusterRole that allows Pods/exec, allows pods/attach, or that does not exist.
`rolebindingRolePodExecAttach` | `warning` | Fails when the RoleBinding references a Role that allows Pods/exec, allows pods/attach, or that does not exist.
`rolebindingClusterRolePodExecAttach` | `warning` | Fails when the RoleBinding references a ClusterRole that allows Pods/exec, allows pods/attach, or that does not exist.
`clusterrolebindingClusterAdmin` | `warning` | Fails when the ClusterRoleBinding references the default cluster-admin ClusterRole or one with wildcard permissions.
`rolebindingClusterAdminClusterRole` | `warning` | Fails when the RoleBinding references the default cluster-admin ClusterRole or one with wildcard permissions.
`rolebindingClusterAdminRole` | `warning` | Fails when the RoleBinding references a Role with wildcard permissions.
## Background
+1891 -1801
View File
File diff suppressed because it is too large Load Diff
+8 -2
View File
@@ -6,17 +6,23 @@ checks:
pullPolicyNotAlways: warning
readinessProbeMissing: warning
livenessProbeMissing: warning
topologySpreadConstraint: warning
pdbDisruptionsIsZero: warning
missingPodDisruptionBudget: ignore
metadataAndNameMismatched: ignore
# efficiency
cpuRequestsMissing: warning
cpuLimitsMissing: warning
memoryRequestsMissing: warning
memoryLimitsMissing: warning
# security
automountServiceAccountToken: warning
hostIPCSet: danger
hostPIDSet: danger
linuxHardening: danger
missingNetworkPolicy: warning
missingNetworkPolicy: ignore
notReadOnlyRootFilesystem: warning
privilegeEscalationAllowed: danger
runAsRootAllowed: danger
@@ -25,6 +31,7 @@ checks:
insecureCapabilities: warning
hostNetworkSet: danger
hostPortSet: warning
tlsSettingsMissing: warning
sensitiveContainerEnvVar: danger
sensitiveConfigmapContent: danger
clusterrolePodExecAttach: danger
@@ -39,7 +46,6 @@ checks:
resourceLimits: warning
imageRegistry: danger
exemptions:
- controllerNames:
- my-network-controller
+1
View File
@@ -16,6 +16,7 @@ checks:
cpuLimitsMissing: warning
memoryRequestsMissing: warning
memoryLimitsMissing: warning
# security
automountServiceAccountToken: ignore
hostIPCSet: danger
+36 -22
View File
@@ -51,32 +51,46 @@ type ResourceProvider struct {
SourceType string
Nodes []corev1.Node
Namespaces []corev1.Namespace
Resources resourceSet
Resources resourceKindMap
}
type resourceSet []GenericResource
type resourceKindMap map[string][]GenericResource
func (rs resourceSet) GetNumberOfControllers() int {
total := 0
func (rkm resourceKindMap) addResource(r GenericResource) {
gvk := r.Resource.GroupVersionKind()
var key string
if gvk.Group != "" {
key = gvk.Group + "/" + gvk.Kind
} else {
key = gvk.Kind
}
rkm[key] = append(rkm[key], r)
}
func (rkm resourceKindMap) addResources(rs []GenericResource) {
for _, r := range rs {
if r.PodSpec != nil {
total++
}
rkm.addResource(r)
}
}
func (rkm resourceKindMap) GetLength() int {
total := 0
for _, rs := range rkm {
total += len(rs)
}
return total
}
func (rs resourceSet) GetAllOfGroupKind(gk string) []GenericResource {
return funk.Filter(rs, func(res GenericResource) bool {
gvk := res.Resource.GroupVersionKind()
var key string
if gvk.Group != "" {
key = gvk.Group + "/" + gvk.Kind
} else {
key = gvk.Kind
func (rkm resourceKindMap) GetNumberOfControllers() int {
total := 0
for _, rs := range rkm {
for _, r := range rs {
if r.PodSpec != nil {
total++
}
}
return key == gk
}).([]GenericResource)
}
return total
}
// This is here for backward compatibility reasons
@@ -108,7 +122,7 @@ func newResourceProvider(version, sourceType, sourceName string) ResourceProvide
CreationTime: time.Now(),
Nodes: make([]corev1.Node, 0),
Namespaces: make([]corev1.Namespace, 0),
Resources: make([]GenericResource, 0),
Resources: make(map[string][]GenericResource),
}
}
@@ -179,7 +193,7 @@ func CreateResourceProviderFromResource(ctx context.Context, workload string) (*
return nil, err
}
resources.Resources = append(resources.Resources, workloadObj)
resources.Resources.addResource(workloadObj)
return &resources, nil
}
@@ -363,7 +377,7 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac
provider.Nodes = nodes.Items
provider.Namespaces = namespaces.Items
provider.Resources = append(provider.Resources, kubernetesResources...)
provider.Resources.addResources(kubernetesResources)
logrus.Info("Done loading Kubernetes resources")
return &provider, nil
}
@@ -465,13 +479,13 @@ func (resources *ResourceProvider) addResourceFromString(contents string) error
return err
}
workload.OriginalObjectYAML = contentBytes
resources.Resources = append(resources.Resources, workload)
resources.Resources.addResource(workload)
} else {
newResource, err := NewGenericResourceFromBytes(contentBytes)
if err != nil {
return err
}
resources.Resources = append(resources.Resources, newResource)
resources.Resources.addResource(newResource)
}
return err
}
+14 -18
View File
@@ -21,11 +21,9 @@ import (
"testing"
"time"
"github.com/thoas/go-funk"
conf "github.com/fairwindsops/polaris/pkg/config"
"github.com/fairwindsops/polaris/test"
"github.com/stretchr/testify/assert"
conf "github.com/fairwindsops/polaris/pkg/config"
)
func TestGetResourcesFromPath(t *testing.T) {
@@ -44,10 +42,12 @@ func TestGetResourcesFromPath(t *testing.T) {
assert.Equal(t, "two", provider.Namespaces[0].ObjectMeta.Name)
namespaceCount := map[string]int{}
for _, controller := range provider.Resources {
namespaceCount[controller.ObjectMeta.GetNamespace()]++
for _, resources := range provider.Resources {
for _, controller := range resources {
namespaceCount[controller.ObjectMeta.GetNamespace()]++
}
}
assert.Equal(t, 11, len(provider.Resources))
assert.Equal(t, 11, provider.Resources.GetLength())
assert.Equal(t, 10, namespaceCount[""])
assert.Equal(t, 1, namespaceCount["two"])
}
@@ -64,11 +64,8 @@ func TestGetMultipleResourceFromSingleFile(t *testing.T) {
assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes")
assert.Equal(t, 6, len(resources.Resources), "Should have 6 resources")
deployment := funk.Find(resources.Resources, func(res GenericResource) bool {
return res.Resource.GroupVersionKind().Kind == "Deployment"
}).(GenericResource)
assert.Equal(t, "dashboard", deployment.PodSpec.Containers[0].Name)
assert.Equal(t, 1, len(resources.Resources["extensions/Deployment"]), "Should have one controller")
assert.Equal(t, "dashboard", resources.Resources["extensions/Deployment"][0].PodSpec.Containers[0].Name)
assert.Equal(t, 2, len(resources.Namespaces), "Should have a namespace")
assert.Equal(t, "polaris", resources.Namespaces[0].ObjectMeta.Name)
@@ -90,11 +87,8 @@ func TestAddResourcesFromReader(t *testing.T) {
assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes")
assert.Equal(t, 6, len(resources.Resources), "Should have 6 resources")
deployment := funk.Find(resources.Resources, func(res GenericResource) bool {
return res.Resource.GroupVersionKind().Kind == "Deployment"
}).(GenericResource)
assert.Equal(t, "dashboard", deployment.PodSpec.Containers[0].Name)
assert.Equal(t, 1, len(resources.Resources["extensions/Deployment"]), "Should have one controller")
assert.Equal(t, "dashboard", resources.Resources["extensions/Deployment"][0].PodSpec.Containers[0].Name)
assert.Equal(t, 2, len(resources.Namespaces), "Should have a namespace")
assert.Equal(t, "polaris", resources.Namespaces[0].ObjectMeta.Name)
@@ -164,8 +158,10 @@ func TestGetResourceFromAPI(t *testing.T) {
assert.Equal(t, 0, len(resources.Nodes), "Should not have any nodes")
assert.Equal(t, 5, len(resources.Resources), "Should have 5 controllers")
for _, ctrl := range resources.Resources {
expectedNames[ctrl.ObjectMeta.GetName()] = true
for _, controllers := range resources.Resources {
for _, ctrl := range controllers {
expectedNames[ctrl.ObjectMeta.GetName()] = true
}
}
for name, val := range expectedNames {
assert.Equal(t, true, val, name)
+3 -6
View File
@@ -72,10 +72,7 @@ func TestControllerLevelChecks(t *testing.T) {
Severity: "danger",
Category: "Reliability",
}
for _, controller := range res.Resources {
if controller.Resource.GroupVersionKind().Kind != "Deployment" {
continue
}
for _, controller := range res.Resources["Deployment"] {
actualResult, err := applyControllerSchemaChecks(&c, nil, controller)
if err != nil {
panic(err)
@@ -99,7 +96,7 @@ func TestControllerLevelChecks(t *testing.T) {
res, err := kube.CreateResourceProviderFromPath("../kube/test_files/test_1")
assert.Equal(t, nil, err, "Error should be nil")
assert.Equal(t, 11, len(res.Resources))
assert.Equal(t, 11, res.Resources.GetLength())
testResources(res)
replicaSpec := map[string]interface{}{"replicas": 2}
@@ -114,7 +111,7 @@ func TestControllerLevelChecks(t *testing.T) {
k8s, dynamicClient := test.SetupTestAPI(&d1, &p1, &d2, &p2)
res, err = kube.CreateResourceProviderFromAPI(context.Background(), k8s, "test", dynamicClient, conf.Configuration{})
assert.Equal(t, err, nil, "error should be nil")
assert.Equal(t, 2, len(res.Resources), "Should have two controllers")
assert.Equal(t, 2, res.Resources.GetLength(), "Should have two controllers")
testResources(res)
}
+10 -2
View File
@@ -173,10 +173,18 @@ func hasExemptionAnnotation(objMeta metaV1.Object, checkID string) bool {
// ApplyAllSchemaChecksToResourceProvider applies all available checks to a ResourceProvider
func ApplyAllSchemaChecksToResourceProvider(conf *config.Configuration, resourceProvider *kube.ResourceProvider) ([]Result, error) {
results := []Result{}
if resourceProvider == nil {
return nil, errors.New("No resource provider set, cannot apply schema checks")
}
return ApplyAllSchemaChecksToAllResources(conf, resourceProvider, resourceProvider.Resources)
for _, resources := range resourceProvider.Resources {
kindResults, err := ApplyAllSchemaChecksToAllResources(conf, resourceProvider, resources)
if err != nil {
return results, err
}
results = append(results, kindResults...)
}
return results, nil
}
// ApplyAllSchemaChecksToAllResources applies available checks to a list of resources
@@ -373,7 +381,7 @@ func applySchemaCheck(conf *config.Configuration, checkID string, test schemaTes
logrus.Warnf("No ResourceProvider available, check %s will not work in this context (e.g. admission control)", checkID)
break
}
resources := test.ResourceProvider.Resources.GetAllOfGroupKind(groupkind)
resources := test.ResourceProvider.Resources[groupkind]
namespace := test.Resource.ObjectMeta.GetNamespace()
if test.Resource.Kind == "Namespace" {
namespace = test.Resource.ObjectMeta.GetName()
+3 -2
View File
@@ -51,8 +51,9 @@ func TestMutations(t *testing.T) {
assert.Len(t, results, 1)
allMutations := mutation.GetMutationsFromResults(results)
assert.Len(t, allMutations, 1)
for _, resource := range tc.resources.Resources {
key := fmt.Sprintf("%s/%s/%s", resource.Kind, resource.Resource.GetName(), resource.Resource.GetNamespace())
for _, resources := range tc.resources.Resources {
assert.Len(t, resources, 1)
key := fmt.Sprintf("%s/%s/%s", resources[0].Kind, resources[0].Resource.GetName(), resources[0].Resource.GetNamespace())
mutations := allMutations[key]
yamlContent, err := mutation.ApplyAllMutations(tc.manifest, mutations)
assert.NoError(t, err)