diff --git a/clouditor-engine-aws/src/main/resources/rules/aws/iam/access-key-rotation-admin.md b/clouditor-engine-aws/src/main/resources/rules/aws/iam/access-key-rotation-admin.md new file mode 100644 index 000000000..85b5f1337 --- /dev/null +++ b/clouditor-engine-aws/src/main/resources/rules/aws/iam/access-key-rotation-admin.md @@ -0,0 +1,13 @@ +# Rotate Access Keys Regularly admin + +Checks if AWS access keys are rotated regularly and are not older than a specified amount of time, i.e. 1000 days. + +```ccl +User with userName=="admin" has createDate younger 90 days in all accessKeys +``` + +## Controls + +* "IAM-02" +* "AWS 1.4" +* "BSI C5/KRY-04" diff --git a/clouditor-engine-aws/src/main/resources/rules/aws/iam/access-key-rotation.md b/clouditor-engine-aws/src/main/resources/rules/aws/iam/access-key-rotation.md index 7b9070391..b902c8f8d 100644 --- a/clouditor-engine-aws/src/main/resources/rules/aws/iam/access-key-rotation.md +++ b/clouditor-engine-aws/src/main/resources/rules/aws/iam/access-key-rotation.md @@ -3,7 +3,7 @@ Checks if AWS access keys are rotated regularly and are not older than a specified amount of time, i.e. 90 days. ```ccl -User has createDate after 90 days in all accessKeys +User has createDate younger 90 days in all accessKeys ``` ## Controls diff --git a/clouditor-engine-aws/src/main/resources/rules/aws/iam/mfa.md b/clouditor-engine-aws/src/main/resources/rules/aws/iam/mfa.md index 12dbb3242..5f6b01ebc 100644 --- a/clouditor-engine-aws/src/main/resources/rules/aws/iam/mfa.md +++ b/clouditor-engine-aws/src/main/resources/rules/aws/iam/mfa.md @@ -3,7 +3,7 @@ Checks if Multi-Factor Authentication (MFA) is enabled for all users. ```ccl -condition: User has not empty mfaDevices +User has not empty mfaDevices ``` ## Controls diff --git a/clouditor-engine-aws/src/test/java/io/clouditor/discovery/aws/AwsIamUserScannerTest.java b/clouditor-engine-aws/src/test/java/io/clouditor/discovery/aws/AwsIamUserScannerTest.java index 305f227f5..9269cdfd5 100644 --- a/clouditor-engine-aws/src/test/java/io/clouditor/discovery/aws/AwsIamUserScannerTest.java +++ b/clouditor-engine-aws/src/test/java/io/clouditor/discovery/aws/AwsIamUserScannerTest.java @@ -184,7 +184,7 @@ void testAccessKeyRotation() throws IOException { FileSystemManager.getInstance() .getPathForResource("rules/aws/iam/access-key-rotation.md")); - // user2 has an very old access key + // user2 has a very old access key assertFalse(rule.evaluate(assets.get(USER2_ARN)).isOk()); } diff --git a/clouditor-engine-core/src/main/antlr/CCL.g4 b/clouditor-engine-core/src/main/antlr/CCL.g4 index 0436143e7..c5bbdfecd 100644 --- a/clouditor-engine-core/src/main/antlr/CCL.g4 +++ b/clouditor-engine-core/src/main/antlr/CCL.g4 @@ -2,18 +2,25 @@ grammar CCL; condition: assetType 'has' expression EOF; -assetType : Identifier; +assetType : + simpleAssetType | + filteredAssetType; + +simpleAssetType: field; + +filteredAssetType: field 'with' expression; + field : Identifier; expression: simpleExpression | - inExpression ; + notExpression | + inExpression; simpleExpression: - '(' expression ')' | - notExpression | emptyExpression | withinExpression | - comparison; + comparison | + '(' expression ')' ; notExpression: 'not' expression; @@ -26,7 +33,9 @@ binaryComparison: field operator value; timeComparison: field timeOperator (time unit | nowOperator); timeOperator: BeforeOperator | - AfterOperator; + AfterOperator | + YoungerOperator | + OlderOperator; nowOperator: 'now'; time: Number; unit: @@ -66,6 +75,8 @@ ContainsOperator: 'contains'; BeforeOperator: 'before'; AfterOperator: 'after'; +YoungerOperator: 'younger'; +OlderOperator: 'older'; BooleanLiteral: True | diff --git a/clouditor-engine-core/src/main/java/io/clouditor/assurance/Rule.java b/clouditor-engine-core/src/main/java/io/clouditor/assurance/Rule.java index 1d93c2ebe..f941cce3a 100644 --- a/clouditor-engine-core/src/main/java/io/clouditor/assurance/Rule.java +++ b/clouditor-engine-core/src/main/java/io/clouditor/assurance/Rule.java @@ -32,9 +32,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import io.clouditor.assurance.ccl.CCLDeserializer; -import io.clouditor.assurance.ccl.CCLSerializer; -import io.clouditor.assurance.ccl.Condition; +import io.clouditor.assurance.ccl.*; import io.clouditor.discovery.Asset; import java.util.ArrayList; import java.util.Collections; @@ -59,6 +57,20 @@ public class Rule { @JsonProperty private List controls = new ArrayList<>(); @JsonProperty private String id; + public boolean evaluateApplicability(Asset asset) { + + if (this.condition != null) { + if (this.condition.getAssetType() instanceof FilteredAssetType) { + return this.condition.getAssetType().evaluate(asset.getProperties()); + } + } else if (this.conditions != null) { + if (this.conditions.get(0).getAssetType() instanceof FilteredAssetType) { + return this.conditions.get(0).getAssetType().evaluate(asset.getProperties()); + } + } + return true; + } + public EvaluationResult evaluate(Asset asset) { var eval = new EvaluationResult(this, asset.getProperties()); @@ -83,14 +95,16 @@ public void setCondition(Condition condition) { public String getAssetType() { // single condition - if (this.condition != null) { - return this.condition.getAssetType(); + if (this.condition != null && this.condition.getAssetType() != null) { + return this.condition.getAssetType().getValue(); } // multiple conditions - if (this.conditions != null && !this.conditions.isEmpty()) { + if (this.conditions != null + && !this.conditions.isEmpty() + && this.conditions.get(0).getAssetType() != null) { // take the first one - return this.conditions.get(0).getAssetType(); + return this.conditions.get(0).getAssetType().getValue(); } // no asset type found, we cannot really use this rule then diff --git a/clouditor-engine-core/src/main/java/io/clouditor/assurance/RuleService.java b/clouditor-engine-core/src/main/java/io/clouditor/assurance/RuleService.java index 2611b0b26..f123bf646 100644 --- a/clouditor-engine-core/src/main/java/io/clouditor/assurance/RuleService.java +++ b/clouditor-engine-core/src/main/java/io/clouditor/assurance/RuleService.java @@ -285,8 +285,13 @@ public void handle(DiscoveryResult result) { // evaluate all rules rulesForAsset.forEach( rule -> { - var eval = rule.evaluate(asset); - + EvaluationResult eval; + if (!rule.evaluateApplicability(asset)) { + // simply add an empty EvaluationResult + eval = new EvaluationResult(rule, asset.getProperties()); + } else { + eval = rule.evaluate(asset); + } // TODO: can we really update the asset? asset.addEvaluationResult(eval); if (!eval.isOk()) { diff --git a/clouditor-engine-core/src/main/java/io/clouditor/assurance/ccl/AssetType.java b/clouditor-engine-core/src/main/java/io/clouditor/assurance/ccl/AssetType.java new file mode 100644 index 000000000..9f0c5b07a --- /dev/null +++ b/clouditor-engine-core/src/main/java/io/clouditor/assurance/ccl/AssetType.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2016-2019, Fraunhofer AISEC. All rights reserved. + * + * + * $$\ $$\ $$\ $$\ + * $$ | $$ |\__| $$ | + * $$$$$$$\ $$ | $$$$$$\ $$\ $$\ $$$$$$$ |$$\ $$$$$$\ $$$$$$\ $$$$$$\ + * $$ _____|$$ |$$ __$$\ $$ | $$ |$$ __$$ |$$ |\_$$ _| $$ __$$\ $$ __$$\ + * $$ / $$ |$$ / $$ |$$ | $$ |$$ / $$ |$$ | $$ | $$ / $$ |$$ | \__| + * $$ | $$ |$$ | $$ |$$ | $$ |$$ | $$ |$$ | $$ |$$\ $$ | $$ |$$ | + * \$$$$$$\ $$ |\$$$$$ |\$$$$$ |\$$$$$$ |$$ | \$$$ |\$$$$$ |$$ | + * \_______|\__| \______/ \______/ \_______|\__| \____/ \______/ \__| + * + * This file is part of Clouditor Community Edition. + * + * Clouditor Community Edition is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Clouditor Community Edition is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * long with Clouditor Community Edition. If not, see + */ + +package io.clouditor.assurance.ccl; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import java.util.Map; + +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "@class") +public abstract class AssetType { + + public abstract boolean evaluate(Map properties); + + public abstract String getValue(); +} diff --git a/clouditor-engine-core/src/main/java/io/clouditor/assurance/ccl/CCLDeserializer.java b/clouditor-engine-core/src/main/java/io/clouditor/assurance/ccl/CCLDeserializer.java index 2317ccd60..f94bed5fd 100644 --- a/clouditor-engine-core/src/main/java/io/clouditor/assurance/ccl/CCLDeserializer.java +++ b/clouditor-engine-core/src/main/java/io/clouditor/assurance/ccl/CCLDeserializer.java @@ -74,7 +74,7 @@ public Condition visitCondition(ConditionContext ctx) { if (ctx.assetType() != null && ctx.expression() != null) { var condition = new Condition(); - condition.setAssetType(ctx.assetType().getText()); + condition.setAssetType(ctx.assetType().accept(new AssetTypeListener())); condition.setExpression(ctx.expression().accept(new ExpressionListener())); return condition; @@ -84,6 +84,33 @@ public Condition visitCondition(ConditionContext ctx) { } } + private static class AssetTypeListener extends CCLBaseVisitor { + + @Override + public AssetType visitAssetType(CCLParser.AssetTypeContext ctx) { + if (ctx.simpleAssetType() != null) { + var simpleAsset = new SimpleAssetType(); + simpleAsset.setValue(ctx.simpleAssetType().field().getText()); + return simpleAsset; + } else if (ctx.filteredAssetType() != null) { + return ctx.filteredAssetType().accept(this); + } + + return super.visitAssetType(ctx); + } + + @Override + public AssetType visitFilteredAssetType(CCLParser.FilteredAssetTypeContext ctx) { + if (ctx.expression() != null) { + var asset = new FilteredAssetType(); + asset.setValue(ctx.field().getText()); + asset.setAssetExpression(ctx.expression().accept(new ExpressionListener())); + return asset; + } + return super.visitFilteredAssetType(ctx); + } + } + private static class ExpressionListener extends CCLBaseVisitor { @Override diff --git a/clouditor-engine-core/src/main/java/io/clouditor/assurance/ccl/Condition.java b/clouditor-engine-core/src/main/java/io/clouditor/assurance/ccl/Condition.java index 55b548e7c..33b571433 100644 --- a/clouditor-engine-core/src/main/java/io/clouditor/assurance/ccl/Condition.java +++ b/clouditor-engine-core/src/main/java/io/clouditor/assurance/ccl/Condition.java @@ -33,7 +33,7 @@ public class Condition { - private String assetType; + private AssetType assetType; private Expression expression; @@ -47,11 +47,11 @@ public void setExpression(Expression expression) { this.expression = expression; } - public String getAssetType() { + public AssetType getAssetType() { return assetType; } - public void setAssetType(String assetType) { + public void setAssetType(AssetType assetType) { this.assetType = assetType; } diff --git a/clouditor-engine-core/src/main/java/io/clouditor/assurance/ccl/FilteredAssetType.java b/clouditor-engine-core/src/main/java/io/clouditor/assurance/ccl/FilteredAssetType.java new file mode 100644 index 000000000..455921120 --- /dev/null +++ b/clouditor-engine-core/src/main/java/io/clouditor/assurance/ccl/FilteredAssetType.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2016-2019, Fraunhofer AISEC. All rights reserved. + * + * + * $$\ $$\ $$\ $$\ + * $$ | $$ |\__| $$ | + * $$$$$$$\ $$ | $$$$$$\ $$\ $$\ $$$$$$$ |$$\ $$$$$$\ $$$$$$\ $$$$$$\ + * $$ _____|$$ |$$ __$$\ $$ | $$ |$$ __$$ |$$ |\_$$ _| $$ __$$\ $$ __$$\ + * $$ / $$ |$$ / $$ |$$ | $$ |$$ / $$ |$$ | $$ | $$ / $$ |$$ | \__| + * $$ | $$ |$$ | $$ |$$ | $$ |$$ | $$ |$$ | $$ |$$\ $$ | $$ |$$ | + * \$$$$$$\ $$ |\$$$$$ |\$$$$$ |\$$$$$$ |$$ | \$$$ |\$$$$$ |$$ | + * \_______|\__| \______/ \______/ \_______|\__| \____/ \______/ \__| + * + * This file is part of Clouditor Community Edition. + * + * Clouditor Community Edition is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Clouditor Community Edition is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * long with Clouditor Community Edition. If not, see + */ + +package io.clouditor.assurance.ccl; + +import java.util.Map; + +public class FilteredAssetType extends AssetType { + + private String value; + private Expression assetExpression; + + public String getValue() { + return this.value; + } + + public void setValue(String value) { + this.value = value; + } + + public void setAssetExpression(Expression assetExpression) { + this.assetExpression = assetExpression; + } + + @Override + public boolean evaluate(Map properties) { + return this.assetExpression.evaluate(properties); + } +} diff --git a/clouditor-engine-core/src/main/java/io/clouditor/assurance/ccl/SimpleAssetType.java b/clouditor-engine-core/src/main/java/io/clouditor/assurance/ccl/SimpleAssetType.java new file mode 100644 index 000000000..c9c328133 --- /dev/null +++ b/clouditor-engine-core/src/main/java/io/clouditor/assurance/ccl/SimpleAssetType.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016-2019, Fraunhofer AISEC. All rights reserved. + * + * + * $$\ $$\ $$\ $$\ + * $$ | $$ |\__| $$ | + * $$$$$$$\ $$ | $$$$$$\ $$\ $$\ $$$$$$$ |$$\ $$$$$$\ $$$$$$\ $$$$$$\ + * $$ _____|$$ |$$ __$$\ $$ | $$ |$$ __$$ |$$ |\_$$ _| $$ __$$\ $$ __$$\ + * $$ / $$ |$$ / $$ |$$ | $$ |$$ / $$ |$$ | $$ | $$ / $$ |$$ | \__| + * $$ | $$ |$$ | $$ |$$ | $$ |$$ | $$ |$$ | $$ |$$\ $$ | $$ |$$ | + * \$$$$$$\ $$ |\$$$$$ |\$$$$$ |\$$$$$$ |$$ | \$$$ |\$$$$$ |$$ | + * \_______|\__| \______/ \______/ \_______|\__| \____/ \______/ \__| + * + * This file is part of Clouditor Community Edition. + * + * Clouditor Community Edition is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Clouditor Community Edition is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * long with Clouditor Community Edition. If not, see + */ + +package io.clouditor.assurance.ccl; + +import java.util.Map; + +public class SimpleAssetType extends AssetType { + + private String value; + + public String getValue() { + return this.value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public boolean evaluate(Map properties) { + return true; + } +} diff --git a/clouditor-engine-core/src/main/java/io/clouditor/assurance/ccl/TimeComparison.java b/clouditor-engine-core/src/main/java/io/clouditor/assurance/ccl/TimeComparison.java index 9f18ef4da..40da935de 100644 --- a/clouditor-engine-core/src/main/java/io/clouditor/assurance/ccl/TimeComparison.java +++ b/clouditor-engine-core/src/main/java/io/clouditor/assurance/ccl/TimeComparison.java @@ -50,24 +50,36 @@ public boolean evaluate(Map properties) { Instant instant; + if (fieldValue == null) { + return false; + } + if (fieldValue instanceof Long) { instant = Instant.ofEpochSecond((Long) fieldValue); - } else { + } else if ((fieldValue instanceof Map) && ((Map) fieldValue).get("epochSecond") != null) { // field values are not really instants but serialized Instant in the form of epochSecond // and nano, we need to re-create the Instant - if (!(fieldValue instanceof Map) || ((Map) fieldValue).get("epochSecond") == null) { - return false; - } instant = Instant.ofEpochSecond( ((Number) ((Map) fieldValue).get("epochSecond")).longValue(), ((Number) ((Map) fieldValue).get("nano")).longValue()); + } else { + try { + instant = Instant.parse((String) fieldValue); + } catch (ClassCastException e) { + return false; + } } - var value = Instant.now().plus(relativeValue, timeUnit); + Instant value; + if (this.timeOperator == TimeOperator.BEFORE || this.timeOperator == TimeOperator.AFTER) { + value = Instant.now().plus(relativeValue, timeUnit); + } else { + value = Instant.now().minus(relativeValue, timeUnit); + } - if (this.timeOperator == TimeOperator.AFTER) { + if (this.timeOperator == TimeOperator.YOUNGER || this.timeOperator == TimeOperator.AFTER) { return instant.isAfter(value); } else { return instant.isBefore(value); @@ -96,6 +108,8 @@ public void setTimeOperator(TimeOperator timeOperator) { public enum TimeOperator { BEFORE, - AFTER + AFTER, + YOUNGER, + OLDER } } diff --git a/clouditor-engine-core/src/test/java/io/clouditor/assurance/CCLDeserializerTest.java b/clouditor-engine-core/src/test/java/io/clouditor/assurance/CCLDeserializerTest.java index 0c54f5a80..a7c285b44 100644 --- a/clouditor-engine-core/src/test/java/io/clouditor/assurance/CCLDeserializerTest.java +++ b/clouditor-engine-core/src/test/java/io/clouditor/assurance/CCLDeserializerTest.java @@ -129,31 +129,78 @@ void testInExpression() { @Test void testIsBeforeComparison() { var ccl = new CCLDeserializer(); - var condition = ccl.parse("User has createDate before now"); + var condition = ccl.parse("AccessKey has expiry before 10 days"); var asset = new AssetProperties(); - asset.put("createDate", Map.of("epochSecond", 1550131042, "nano", 1)); + asset.put("expiry", Map.of("epochSecond", Instant.now().getEpochSecond(), "nano", 1)); assertTrue(condition.evaluate(asset)); asset.clear(); - // something in the future - asset.put("createDate", Map.of("epochSecond", Integer.MAX_VALUE, "nano", 1)); + + asset.put( + "expiry", + Map.of("epochSecond", Instant.now().plus(20, ChronoUnit.DAYS).getEpochSecond(), "nano", 1)); + + assertFalse(condition.evaluate(asset)); + } + + @Test + void testAfterComparison() { + var ccl = new CCLDeserializer(); + var condition = ccl.parse("AccessKey has expiry after 10 days"); + + var asset = new AssetProperties(); + asset.put( + "expiry", + Map.of("epochSecond", Instant.now().plus(20, ChronoUnit.DAYS).getEpochSecond(), "nano", 1)); + + assertTrue(condition.evaluate(asset)); + + asset.clear(); + + asset.put("expiry", Map.of("epochSecond", Instant.now().getEpochSecond(), "nano", 1)); assertFalse(condition.evaluate(asset)); + } + + @Test + void testYoungerComparison() { + var ccl = new CCLDeserializer(); + var condition = ccl.parse("User has createDate younger 10 days"); - condition = ccl.parse("User has createDate after now"); + var asset = new AssetProperties(); + asset.put("createDate", Map.of("epochSecond", Instant.now().getEpochSecond(), "nano", 1)); assertTrue(condition.evaluate(asset)); - // something older than 90 days + asset.clear(); + // something in the future + asset.put( + "createDate", + Map.of( + "epochSecond", Instant.now().minus(20, ChronoUnit.DAYS).getEpochSecond(), "nano", 1)); + + assertFalse(condition.evaluate(asset)); + } + + @Test + void testOlderComparison() { + var ccl = new CCLDeserializer(); + var condition = ccl.parse("User has createDate older 10 days"); + + var asset = new AssetProperties(); + asset.put( "createDate", Map.of( - "epochSecond", Instant.now().minus(100, ChronoUnit.DAYS).getEpochSecond(), "nano", 0)); + "epochSecond", Instant.now().minus(20, ChronoUnit.DAYS).getEpochSecond(), "nano", 1)); + + assertTrue(condition.evaluate(asset)); + + asset.clear(); - // user create date should not be older than 90 days - condition = ccl.parse("User has not createDate before 90 days"); + asset.put("createDate", Map.of("epochSecond", Instant.now().getEpochSecond(), "nano", 1)); assertFalse(condition.evaluate(asset)); }