Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(conftest): Associate Rego policies with K8s resources #752

Merged
merged 1 commit into from
Oct 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ var (

//go:embed testdata/run_as_root.rego
runAsRootPolicy string
//go:embed testdata/service_with_external_ip.rego
serviceWithExternalIPPolicy string
)

func TestIntegrationOperatorWithConftest(t *testing.T) {
Expand Down Expand Up @@ -111,7 +113,10 @@ var _ = BeforeSuite(func() {
Data: map[string]string{
"conftest.imageRef": "docker.io/openpolicyagent/conftest:v0.25.0",

"conftest.policy.runs_as_root.rego": runAsRootPolicy,
"conftest.policy.runs_as_root.rego": runAsRootPolicy,
"conftest.policy.runs_as_root.kinds": "Workload",
"conftest.policy.service_with_external_ip.rego": serviceWithExternalIPPolicy,
"conftest.policy.service_with_external_ip.kinds": "Service",
},
}
err = kubeClient.Create(context.Background(), conftestCM)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
package main
package kubernetes.configaudit.run_as_root

deny[res] {
input.kind == "ReplicaSet"
not input.spec.template.spec.securityContext.runAsNonRoot
input.kind == "ReplicaSet"
not input.spec.template.spec.securityContext.runAsNonRoot

res := {
"msg": "Containers must not run as root",
"title": "Run as root"
}
res := {
"msg": "Containers must not run as root",
"title": "Run as root",
}
}

deny[res] {
input.kind == "Pod"
not input.spec.securityContext.runAsNonRoot
input.kind == "Pod"
not input.spec.securityContext.runAsNonRoot

res := {
"msg": "Containers must not run as root",
"title": "Run as root"
}
res := {
"msg": "Containers must not run as root",
"title": "Run as root",
}
}

deny[res] {
input.kind == "CronJob"
not input.spec.jobTemplate.spec.template.spec.securityContext.runAsNonRoot
input.kind == "CronJob"
not input.spec.jobTemplate.spec.template.spec.securityContext.runAsNonRoot

res := {
"msg": "Containers must not run as root",
"title": "Run as root"
}
res := {
"msg": "Containers must not run as root",
"title": "Run as root",
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package kubernetes.configaudit.service_with_external_ip

deny[res] {
input.kind == "Service"
count(input.spec.externalIPs) > 0
res := {
"msg": "Service with external IP",
"title": "Service with external IP",
}
}
3 changes: 2 additions & 1 deletion itest/starboard/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ var (
Namespace: "starboard",
},
Data: map[string]string{
"conftest.imageRef": "docker.io/openpolicyagent/conftest:v0.25.0",
"conftest.imageRef": "docker.io/openpolicyagent/conftest:v0.25.0",
"conftest.policy.runs_as_root.kinds": "Workload",
"conftest.policy.runs_as_root.rego": `
package main
Expand Down
2 changes: 1 addition & 1 deletion pkg/configauditreport/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func (s *ScanJobBuilder) Get() (*batchv1.Job, []*corev1.Secret, error) {

jobSpec.Tolerations = append(jobSpec.Tolerations, s.tolerations...)

pluginConfigHash, err := s.plugin.GetConfigHash(s.pluginContext)
pluginConfigHash, err := s.plugin.ConfigHash(s.pluginContext, kube.Kind(s.object.GetObjectKind().GroupVersionKind().Kind))
if err != nil {
return nil, nil, err
}
Expand Down
10 changes: 5 additions & 5 deletions pkg/configauditreport/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,12 @@ type testPlugin struct {
configHash string
}

func (p *testPlugin) SupportsKind(_ kube.Kind) bool {
return true
func (p *testPlugin) SupportedKinds() []kube.Kind {
return []kube.Kind{}
}

func (p *testPlugin) IsReady(_ starboard.PluginContext) (bool, error) {
return true, nil
func (p *testPlugin) IsApplicable(_ starboard.PluginContext, _ client.Object) (bool, string, error) {
return true, "", nil
}

func (p *testPlugin) Init(_ starboard.PluginContext) error {
Expand All @@ -143,7 +143,7 @@ func (p *testPlugin) GetContainerName() string {
return ""
}

func (p *testPlugin) GetConfigHash(_ starboard.PluginContext) (string, error) {
func (p *testPlugin) ConfigHash(_ starboard.PluginContext, _ kube.Kind) (string, error) {
return p.configHash, nil
}

Expand Down
18 changes: 9 additions & 9 deletions pkg/configauditreport/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ type Plugin interface {
// to read logs from.
GetContainerName() string

// GetConfigHash returns hash of the plugin's configuration settings. The computed hash
// is used to invalidate v1alpha1.ConfigAuditReport object whenever configuration changes.
GetConfigHash(ctx starboard.PluginContext) (string, error)
// ConfigHash returns hash of the plugin's configuration settings. The computed hash
// is used to invalidate v1alpha1.ConfigAuditReport and v1alpha1.ClusterConfigAuditReport
// objects whenever configuration applicable to the specified resource kind changes.
ConfigHash(ctx starboard.PluginContext, kind kube.Kind) (string, error)

// SupportsKind returns true if the given resource kind is supported by
// this plugin, false otherwise.
SupportsKind(kind kube.Kind) bool
// SupportedKinds returns kinds supported by this plugin.
SupportedKinds() []kube.Kind

// IsReady returns true if the plugin is ready for reconciliation, false
// otherwise.
IsReady(ctx starboard.PluginContext) (bool, error)
// IsApplicable return true if the given object can be scanned by this
// plugin, false otherwise.
IsApplicable(ctx starboard.PluginContext, obj client.Object) (bool, string, error)
}
19 changes: 18 additions & 1 deletion pkg/configauditreport/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,22 @@ func NewScanner(
}

func (s *Scanner) Scan(ctx context.Context, partial kube.Object) (*ReportBuilder, error) {
if !s.plugin.SupportsKind(partial.Kind) {
if !s.supportsKind(partial.Kind) {
return nil, fmt.Errorf("kind %s is not supported by %s plugin", partial.Kind, s.pluginContext.GetName())
}
obj, err := s.objectResolver.GetObjectFromPartialObject(ctx, partial)
if err != nil {
return nil, err
}

applicable, reason, err := s.plugin.IsApplicable(s.pluginContext, obj)
if err != nil {
return nil, err
}
if !applicable {
return nil, fmt.Errorf("not applicable: %s", reason)
}

owner, err := s.objectResolver.ReportOwner(ctx, obj)
if err != nil {
return nil, err
Expand Down Expand Up @@ -127,3 +135,12 @@ func (s *Scanner) Scan(ctx context.Context, partial kube.Object) (*ReportBuilder
PluginConfigHash(pluginConfigHash).
Data(result), nil
}

func (s *Scanner) supportsKind(kind kube.Kind) bool {
for _, k := range s.plugin.SupportedKinds() {
if k == kind {
return true
}
}
return false
}
42 changes: 26 additions & 16 deletions pkg/operator/controller/configauditreport.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func (r *ConfigAuditReportReconciler) SetupWithManager(mgr ctrl.Manager) error {
}

for _, resource := range resources {
if !r.Plugin.SupportsKind(resource.kind) {
if !r.supportsKind(resource.kind) {
r.Logger.Info("Skipping unsupported kind", "pluginName", r.PluginContext.GetName(), "kind", resource.kind)
continue
}
Expand All @@ -94,7 +94,7 @@ func (r *ConfigAuditReportReconciler) SetupWithManager(mgr ctrl.Manager) error {
}

for _, resource := range clusterResources {
if !r.Plugin.SupportsKind(resource.kind) {
if !r.supportsKind(resource.kind) {
r.Logger.Info("Skipping unsupported kind", "pluginName", r.PluginContext.GetName(), "kind", resource.kind)
continue
}
Expand All @@ -120,23 +120,19 @@ func (r *ConfigAuditReportReconciler) SetupWithManager(mgr ctrl.Manager) error {
Complete(r.reconcileJobs())
}

func (r *ConfigAuditReportReconciler) supportsKind(kind kube.Kind) bool {
for _, k := range r.Plugin.SupportedKinds() {
if k == kind {
return true
}
}
return false
}

func (r *ConfigAuditReportReconciler) reconcileResource(resourceKind kube.Kind) reconcile.Func {
return func(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := r.Logger.WithValues("kind", resourceKind, "name", req.NamespacedName)

ready, err := r.Plugin.IsReady(r.PluginContext)
if err != nil {
return ctrl.Result{}, fmt.Errorf("checking whether plugin is ready: %w", err)
}
if !ready {
log.V(1).Info("Pushing back reconcile key",
"reason", "plugin not ready",
"pluginName", r.PluginContext.GetName(),
"retryAfter", r.ScanJobRetryAfter)
// TODO Introduce more generic param to retry processing a given key.
return ctrl.Result{RequeueAfter: r.Config.ScanJobRetryAfter}, nil
}

resourcePartial := kube.GetPartialObjectFromKindAndNamespacedName(resourceKind, req.NamespacedName)

log.V(1).Info("Getting resource from cache")
Expand Down Expand Up @@ -169,12 +165,26 @@ func (r *ConfigAuditReportReconciler) reconcileResource(resourceKind kube.Kind)
}
}

// Skip processing if plugin is not applicable to this object
applicable, reason, err := r.Plugin.IsApplicable(r.PluginContext, resource)
if err != nil {
return ctrl.Result{}, fmt.Errorf("checking whether plugin is applicable: %w", err)
}
if !applicable {
log.V(1).Info("Pushing back reconcile key",
"reason", reason,
"pluginName", r.PluginContext.GetName(),
"retryAfter", r.ScanJobRetryAfter)
// TODO Introduce more generic param to retry processing a given key.
return ctrl.Result{RequeueAfter: r.Config.ScanJobRetryAfter}, nil
}

resourceSpecHash, err := kube.ComputeSpecHash(resource)
if err != nil {
return ctrl.Result{}, fmt.Errorf("computing spec hash: %w", err)
}

pluginConfigHash, err := r.Plugin.GetConfigHash(r.PluginContext)
pluginConfigHash, err := r.Plugin.ConfigHash(r.PluginContext, kube.Kind(resource.GetObjectKind().GroupVersionKind().Kind))
if err != nil {
return ctrl.Result{}, fmt.Errorf("computing plugin config hash: %w", err)
}
Expand Down
Loading