This repository has been archived by the owner on Oct 30, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 189
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #75 from Shopify/autofix
Add autofix command
- Loading branch information
Showing
64 changed files
with
1,054 additions
and
222 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package cmd | ||
|
||
import k8sRuntime "k8s.io/apimachinery/pkg/runtime" | ||
|
||
func fixAllowPrivilegeEscalation(resource k8sRuntime.Object) k8sRuntime.Object { | ||
var containers []Container | ||
for _, container := range getContainers(resource) { | ||
container.SecurityContext.AllowPrivilegeEscalation = newFalse() | ||
containers = append(containers, container) | ||
} | ||
return setContainers(resource, containers) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package cmd | ||
|
||
import "testing" | ||
|
||
func TestFixAllowPrivilegeEscalation(t *testing.T) { | ||
assert, resource := FixTestSetup(t, "allow_privilege_escalation_nil.yml", auditAllowPrivilegeEscalation) | ||
for _, container := range getContainers(resource) { | ||
assert.False(*container.SecurityContext.AllowPrivilegeEscalation) | ||
} | ||
} | ||
|
||
func TestFixAllowPrivilegeEscalationTrue(t *testing.T) { | ||
assert, resource := FixTestSetup(t, "allow_privilege_escalation_true.yml", auditAllowPrivilegeEscalation) | ||
for _, container := range getContainers(resource) { | ||
assert.False(*container.SecurityContext.AllowPrivilegeEscalation) | ||
} | ||
} | ||
|
||
func TestFixAllowPrivilegeEscalationTrueAllowed(t *testing.T) { | ||
assert, resource := FixTestSetup(t, "allow_privilege_escalation_true_allowed.yml", auditAllowPrivilegeEscalation) | ||
for _, container := range getContainers(resource) { | ||
assert.True(*container.SecurityContext.AllowPrivilegeEscalation) | ||
} | ||
} | ||
|
||
func TestFixAllowPrivilegeEscalationMisconfiguredAllow(t *testing.T) { | ||
assert, resource := FixTestSetup(t, "allow_privilege_escalation_misconfigured_allow.yml", auditAllowPrivilegeEscalation) | ||
for _, container := range getContainers(resource) { | ||
assert.False(*container.SecurityContext.AllowPrivilegeEscalation) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package cmd | ||
|
||
import ( | ||
log "github.com/sirupsen/logrus" | ||
"github.com/spf13/cobra" | ||
k8sRuntime "k8s.io/apimachinery/pkg/runtime" | ||
) | ||
|
||
func getAuditFunctions() []interface{} { | ||
return []interface{}{ | ||
auditAllowPrivilegeEscalation, auditReadOnlyRootFS, auditRunAsNonRoot, | ||
auditAutomountServiceAccountToken, auditPrivileged, auditCapabilities, | ||
} | ||
} | ||
|
||
func runAllAudits(resource k8sRuntime.Object) (results []Result) { | ||
for _, function := range getAuditFunctions() { | ||
for _, result := range getResults([]k8sRuntime.Object{resource}, function) { | ||
results = append(results, result) | ||
} | ||
} | ||
return results | ||
} | ||
|
||
func fixPotentialSecurityIssue(resource k8sRuntime.Object, result Result) k8sRuntime.Object { | ||
resource = fixSecurityContextNIL(resource) | ||
resource = fixCapabilitiesNIL(resource) | ||
for _, occurrence := range result.Occurrences { | ||
switch occurrence.id { | ||
case ErrorAllowPrivilegeEscalationNIL, ErrorAllowPrivilegeEscalationTrue: | ||
resource = fixAllowPrivilegeEscalation(resource) | ||
case ErrorCapabilityNotDropped: | ||
resource = fixCapabilityNotDropped(resource, occurrence) | ||
case ErrorCapabilityAdded: | ||
resource = fixCapabilityAdded(resource, occurrence) | ||
case ErrorPrivilegedNIL, ErrorPrivilegedTrue: | ||
resource = fixPrivileged(resource) | ||
case ErrorReadOnlyRootFilesystemFalse, ErrorReadOnlyRootFilesystemNIL: | ||
resource = fixReadOnlyRootFilesystem(resource) | ||
case ErrorRunAsNonRootFalse, ErrorRunAsNonRootNIL: | ||
resource = fixRunAsNonRoot(resource) | ||
case ErrorServiceAccountTokenDeprecated: | ||
resource = fixDeprecatedServiceAccount(resource) | ||
case ErrorAutomountServiceAccountTokenTrueAndNoName, ErrorAutomountServiceAccountTokenNILAndNoName: | ||
resource = fixServiceAccountToken(resource) | ||
} | ||
} | ||
return resource | ||
} | ||
|
||
func fix(resources []k8sRuntime.Object) (fixedResources []k8sRuntime.Object) { | ||
for _, resource := range resources { | ||
results := runAllAudits(resource) | ||
for _, result := range results { | ||
resource = fixPotentialSecurityIssue(resource, result) | ||
} | ||
fixedResources = append(fixedResources, resource) | ||
} | ||
return | ||
} | ||
|
||
func autofix(*cobra.Command, []string) { | ||
resources, err := getKubeResourcesManifest(rootConfig.manifest) | ||
if err != nil { | ||
log.Error(err) | ||
} | ||
fixedResources := fix(resources) | ||
err = writeManifestFile(fixedResources, rootConfig.manifest) | ||
if err != nil { | ||
return | ||
} | ||
} | ||
|
||
var autofixCmd = &cobra.Command{ | ||
Use: "autofix", | ||
Short: "Automagically fixes a manifest to be secure", | ||
Long: `"autofix" will examine a manifest file and automagically fill in the blanks to leave your yaml file more secure than it found it | ||
Example usage: | ||
kubeaudit autofix -f /path/to/yaml`, | ||
Run: autofix, | ||
} | ||
|
||
func init() { | ||
RootCmd.AddCommand(autofixCmd) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package cmd | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
|
||
"github.com/go-test/deep" | ||
) | ||
|
||
func TestFix(t *testing.T) { | ||
file := "../fixtures/autofix.yml" | ||
fileFixed := "../fixtures/autofix-fixed.yml" | ||
assert := assert.New(t) | ||
resources, err := getKubeResourcesManifest(file) | ||
assert.Nil(err) | ||
fixedResources := fix(resources) | ||
correctlyFixedResources, err := getKubeResourcesManifest(fileFixed) | ||
assert.Nil(err) | ||
assert.Nil(deep.Equal(correctlyFixedResources, fixedResources)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package cmd | ||
|
||
import k8sRuntime "k8s.io/apimachinery/pkg/runtime" | ||
|
||
func fixServiceAccountToken(resource k8sRuntime.Object) k8sRuntime.Object { | ||
return setASAT(resource, false) | ||
} | ||
|
||
func fixDeprecatedServiceAccount(resource k8sRuntime.Object) k8sRuntime.Object { | ||
return disableDSA(resource) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package cmd | ||
|
||
import "testing" | ||
|
||
func TestFixServiceAccountTokenDeprecated(t *testing.T) { | ||
assert, resource := FixTestSetup(t, "service_account_token_deprecated.yml", auditAutomountServiceAccountToken) | ||
switch typ := resource.(type) { | ||
case *ReplicationController: | ||
assert.Equal("", typ.Spec.Template.Spec.DeprecatedServiceAccount) | ||
} | ||
} | ||
|
||
func TestFixServiceAccountTokenTrueAndNoName(t *testing.T) { | ||
assert, resource := FixTestSetup(t, "service_account_token_true_and_no_name.yml", auditAutomountServiceAccountToken) | ||
switch typ := resource.(type) { | ||
case *ReplicationController: | ||
assert.False(*typ.Spec.Template.Spec.AutomountServiceAccountToken) | ||
} | ||
} | ||
|
||
func TestFixServiceAccountTokenNILAndNoName(t *testing.T) { | ||
assert, resource := FixTestSetup(t, "service_account_token_nil_and_no_name.yml", auditAutomountServiceAccountToken) | ||
switch typ := resource.(type) { | ||
case *ReplicationController: | ||
assert.False(*typ.Spec.Template.Spec.AutomountServiceAccountToken) | ||
} | ||
} | ||
|
||
func TestFixServiceAccountTokenTrueAllowed(t *testing.T) { | ||
assert, resource := FixTestSetup(t, "service_account_token_true_allowed.yml", auditAutomountServiceAccountToken) | ||
switch typ := resource.(type) { | ||
case *ReplicationController: | ||
assert.True(*typ.Spec.Template.Spec.AutomountServiceAccountToken) | ||
} | ||
} | ||
|
||
func TestFixServiceAccountTokenMisconfiguredAllow(t *testing.T) { | ||
assert, resource := FixTestSetup(t, "service_account_token_misconfigured_allow.yml", auditAutomountServiceAccountToken) | ||
switch typ := resource.(type) { | ||
case *ReplicationController: | ||
assert.False(*typ.Spec.Template.Spec.AutomountServiceAccountToken) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package cmd | ||
|
||
import "sort" | ||
|
||
type CapSet map[Capability]bool | ||
|
||
func NewCapSetFromArray(array []Capability) (set CapSet) { | ||
set = make(CapSet) | ||
for _, cap := range array { | ||
set[cap] = true | ||
} | ||
return | ||
} | ||
|
||
func mergeCapSets(sets ...CapSet) (merged CapSet) { | ||
merged = make(CapSet) | ||
for _, set := range sets { | ||
for k, v := range set { | ||
merged[k] = v | ||
} | ||
} | ||
return | ||
} | ||
|
||
func sortCapSet(capSet CapSet) (sorted []Capability) { | ||
keys := []string{} | ||
for key := range capSet { | ||
keys = append(keys, string(key)) | ||
} | ||
sort.Strings(keys) | ||
|
||
for _, key := range keys { | ||
sorted = append(sorted, Capability(key)) | ||
} | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package cmd | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNewCapSetFromArray(t *testing.T) { | ||
assert := assert.New(t) | ||
capArray := []Capability{"AUDIT_WRITE", "CHOWN"} | ||
capSet := CapSet{"AUDIT_WRITE": true, "CHOWN": true} | ||
assert.Equal(NewCapSetFromArray(capArray), capSet) | ||
} | ||
|
||
func TestMergeCapSets(t *testing.T) { | ||
assert := assert.New(t) | ||
set1 := CapSet{"AUDIT_WRITE": true, "CHOWN": true} | ||
set2 := CapSet{"CHOWN": true, "DAC_OVERRIDE": true} | ||
set3 := CapSet{"AUDIT_WRITE": true, "CHOWN": true, "DAC_OVERRIDE": true} | ||
assert.Equal(mergeCapSets(set1, set2), set3) | ||
} | ||
|
||
func TestSortCapSet(t *testing.T) { | ||
assert := assert.New(t) | ||
sorted := sortCapSet(CapSet{"DAC_OVVERRIDE": true, "AUDIT_WRITE": true, "CHOWN": true}) | ||
assert.Equal([]Capability{"AUDIT_WRITE", "CHOWN", "DAC_OVVERRIDE"}, sorted) | ||
} |
Oops, something went wrong.