Skip to content

Commit

Permalink
[linting] check permissions to create pods/access secrets + enables t…
Browse files Browse the repository at this point in the history
…o exclude linting rules from the manifest or descriptor
  • Loading branch information
rmannibucau committed Aug 19, 2023
1 parent 2917449 commit 66bd8ae
Show file tree
Hide file tree
Showing 12 changed files with 450 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.yupiik.bundlebee.core.command.impl.lint.LintError;
import io.yupiik.bundlebee.core.command.impl.lint.LintingCheck;
import io.yupiik.bundlebee.core.configuration.Description;
import io.yupiik.bundlebee.core.descriptor.Manifest;
import io.yupiik.bundlebee.core.kube.KubeClient;
import io.yupiik.bundlebee.core.service.AlveolusHandler;
import io.yupiik.bundlebee.core.service.ArchiveReader;
Expand All @@ -31,17 +32,22 @@
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import javax.json.JsonObject;
import javax.json.JsonString;
import javax.json.JsonValue;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletionStage;
import java.util.logging.Level;
import java.util.stream.Stream;

import static io.yupiik.bundlebee.core.lang.CompletionFutures.all;
import static java.util.Optional.ofNullable;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static javax.json.JsonValue.EMPTY_JSON_ARRAY;

@Log
@Dependent
Expand Down Expand Up @@ -145,15 +151,25 @@ private void doLint(final AlveolusHandler.AlveolusContext ctx,
final String descriptor,
final JsonObject desc,
final LintErrors result,
final List<LintingCheck> checks) {
final List<LintingCheck> checks, final Manifest manifest) {
if (ignoredAlveoli.contains(ctx.getAlveolus().getName()) || ignoredDescriptors.contains(descriptor)) {
log.finest(() -> "Ignoring '" + descriptor + "' from alveolus '" + ctx.getAlveolus().getName() + "'");
return;
}

final var ignored = ofNullable(desc.getJsonArray("$bundlebeeIgnoredLintingRules")).orElse(EMPTY_JSON_ARRAY);

log.finest(() -> "Linting " + ctx.getAlveolus().getName() + ": " + desc);
final var ld = new LintingCheck.LintableDescriptor(descriptor, desc);
final var ld = new LintingCheck.LintableDescriptor(ctx.getAlveolus().getName(), descriptor, desc);
result.errors.addAll(checks.stream()
// excluded validations from the manifest
.filter(c -> manifest.getIgnoredLintingRules() == null || manifest.getIgnoredLintingRules().stream()
.noneMatch(r -> Objects.equals(r.getName(), c.name())))
// excluded validations from the descriptor itself in $bundlebeeIgnoredLintingRules attribute
.filter(c -> ignored.isEmpty() || ignored.stream()
.filter(it -> it.getValueType() == JsonValue.ValueType.STRING)
.map(it -> ((JsonString) it).getString())
.noneMatch(excluded -> Objects.equals(excluded, c.name())))
.filter(c -> c.accept(ld))
.flatMap(c -> c.validate(ld)
.map(it -> new DecoratedLintError(it, ctx.getAlveolus().getName(), descriptor, c.remediation())))
Expand Down Expand Up @@ -191,7 +207,7 @@ private void postProcess(final LintErrors result) {
});
}

private CompletionStage<? extends List<?>> visit(final LintErrors result) {
private CompletionStage<Void> visit(final LintErrors result) {
final var cache = archives.newCache();
final var checks = this.checks.stream()
.filter(it -> {
Expand All @@ -208,27 +224,30 @@ private CompletionStage<? extends List<?>> visit(final LintErrors result) {
.map(it -> visitor.executeOnceOnAlveolus(
null, it.getManifest(), it.getAlveolus(), null,
(ctx, desc) -> k8s.forDescriptor("Linting", desc.getContent(), desc.getExtension(), json -> {
doLint(ctx, desc.getConfiguration().getName(), json, result, checks);
doLint(ctx, desc.getConfiguration().getName(), json, result, checks, it.getManifest());
return completedFuture(true);
}),
cache, null, "inspected"))
.collect(toList()), toList(),
true));
true))
.thenRun(() -> result.errors.addAll(checks.stream()
.flatMap(c -> c.afterAll().map(e -> new DecoratedLintError(e, e.getAlveolus(), e.getDescriptor(), c.remediation())))
.collect(toList())));
}

private static class LintErrors extends RuntimeException {
private final List<DecoratedLintError> errors = new ArrayList<>();

public LintErrors() {
super("There are linting errors");
super("Linting errors");
}

@Override
public String getMessage() {
return super.getMessage() + ": " + (errors.isEmpty() ? "no." : errors.stream()
return super.getMessage() + (errors.isEmpty() ? ": no." : (":" + errors.stream()
.map(e -> "- [" + e.getError().getLevel().name() + "]" + e.format())
.sorted()
.collect(joining("\n", "\n", "\n")));
.collect(joining("\n", "\n", "\n"))));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2021-2023 - Yupiik SAS - https://www.yupiik.com
* 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 io.yupiik.bundlebee.core.command.impl.lint;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;

@Getter
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ContextualLintError extends LintError {
private final String alveolus;
private final String descriptor;

public ContextualLintError(final LintLevel level, final String message, final String alveolus, final String descriptor) {
super(level, message);
this.alveolus = alveolus;
this.descriptor = descriptor;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@
import lombok.RequiredArgsConstructor;

import javax.json.JsonObject;
import javax.json.JsonValue;
import java.util.stream.Stream;

import static java.util.Optional.ofNullable;

public interface LintingCheck {
String name();

Expand All @@ -32,18 +35,46 @@ public interface LintingCheck {

Stream<LintError> validate(LintableDescriptor descriptor);

/**
* Enables to create validations with dependencies between descriptors, use {@link #accept(LintableDescriptor)} as a visitor.
*
* @return validation errors after all descriptors got visited.
*/
default Stream<ContextualLintError> afterAll() {
return Stream.empty();
}

@Getter
@RequiredArgsConstructor
class LintableDescriptor {
private final String alveolus;
private final String name;
private final JsonObject descriptor;

public boolean isKind(final String kind) {
try {
return descriptor.getString("kind", "").equals(kind);
return kind().equals(kind);
} catch (final RuntimeException re) {
return false;
}
}

public String kind() {
return descriptor.getString("kind", "");
}

public String name() {
return ofNullable(descriptor.getJsonObject("metadata"))
.map(JsonValue::asJsonObject)
.map(o -> o.getString("name", ""))
.orElse("");
}

public String namespace() {
return ofNullable(descriptor.getJsonObject("metadata"))
.map(JsonValue::asJsonObject)
.map(o -> o.getString("namespace", ""))
.orElse("");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2021-2023 - Yupiik SAS - https://www.yupiik.com
* 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 io.yupiik.bundlebee.core.command.impl.lint.builtin;

import javax.enterprise.context.Dependent;
import java.util.Set;

@Dependent
public class AccessToCreatePods extends AccessToResources {
public AccessToCreatePods() {
super(
Set.of("pods", "deployments", "statefulsets", "replicasets", "cronjob", "jobs", "daemonsets"),
Set.of("create"),
Set.of("ClusterRoleBinding", "RoleBinding"));
}

@Override
public String name() {
return "access-to-create-pods";
}

@Override
public String description() {
return "Indicates when a subject (Group/User/ServiceAccount) has create access to Pods.\n" +
"CIS Benchmark 5.1.4: The ability to create pods in a cluster opens up possibilities for privilege escalation and should be restricted, where possible.";
}

@Override
public String remediation() {
return "Where possible, remove create access to pod objects in the cluster.";
}
}

0 comments on commit 66bd8ae

Please sign in to comment.