Skip to content

Commit a92edcc

Browse files
SONARPY-1014 Rule S6245: Disabling server-side encryption of S3 bucket is security-sensitive (#1128)
1 parent 5f73323 commit a92edcc

File tree

7 files changed

+240
-2
lines changed

7 files changed

+240
-2
lines changed

python-checks/src/main/java/org/sonar/python/checks/CheckList.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
import java.util.Arrays;
2323
import java.util.Collections;
2424
import java.util.HashSet;
25+
2526
import org.sonar.python.checks.cdk.S3BucketBlockPublicAccessCheck;
27+
import org.sonar.python.checks.cdk.S3BucketServerEncryptionCheck;
2628
import org.sonar.python.checks.cdk.S3BucketVersioningCheck;
2729
import org.sonar.python.checks.hotspots.ClearTextProtocolsCheck;
2830
import org.sonar.python.checks.hotspots.CommandLineArgsCheck;
@@ -227,6 +229,7 @@ public static Iterable<Class> getChecks() {
227229
ReturnYieldOutsideFunctionCheck.class,
228230
RobustCipherAlgorithmCheck.class,
229231
S3BucketBlockPublicAccessCheck.class,
232+
S3BucketServerEncryptionCheck.class,
230233
S3BucketVersioningCheck.class,
231234
SameBranchCheck.class,
232235
SameConditionCheck.class,
@@ -279,8 +282,7 @@ public static Iterable<Class> getChecks() {
279282
WeakSSLProtocolCheck.class,
280283
WildcardImportCheck.class,
281284
WrongAssignmentOperatorCheck.class,
282-
XMLParserXXEVulnerableCheck.class
283-
)));
285+
XMLParserXXEVulnerableCheck.class)));
284286
}
285287

286288
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2022 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.python.checks.cdk;
21+
22+
import java.util.Optional;
23+
import org.sonar.check.Rule;
24+
import org.sonar.plugins.python.api.SubscriptionContext;
25+
import org.sonar.plugins.python.api.symbols.Symbol;
26+
import org.sonar.plugins.python.api.tree.CallExpression;
27+
import org.sonar.plugins.python.api.tree.Expression;
28+
import org.sonar.plugins.python.api.tree.QualifiedExpression;
29+
import org.sonar.python.tree.QualifiedExpressionImpl;
30+
31+
@Rule(key = "S6245")
32+
public class S3BucketServerEncryptionCheck extends AbstractS3BucketCheck {
33+
34+
private static final String S3_BUCKET_UNENCRYPTED_FQN = "aws_cdk.aws_s3.BucketEncryption.UNENCRYPTED";
35+
public static final String MESSAGE = "Objects in the bucket are not encrypted. Make sure it is safe here.";
36+
public static final String OMITTING_MESSAGE = "Omitting 'encryption' disables server-side encryption. Make sure it is safe here.";
37+
38+
@Override
39+
void visitBucketConstructor(SubscriptionContext ctx, CallExpression bucket) {
40+
Optional<ArgumentTrace> optEncryptionType = getArgument(ctx, bucket, "encryption");
41+
if (optEncryptionType.isPresent()) {
42+
optEncryptionType.ifPresent(argumentTrace -> argumentTrace.addIssueIf(S3BucketServerEncryptionCheck::isUnencrypted, MESSAGE));
43+
} else {
44+
Optional<ArgumentTrace> optEncryptionKey = getArgument(ctx, bucket, "encryption_key");
45+
if (!optEncryptionKey.isPresent()) {
46+
ctx.addIssue(bucket.callee(), OMITTING_MESSAGE);
47+
}
48+
}
49+
}
50+
51+
protected static boolean isUnencrypted(Expression expression) {
52+
return Optional.of(expression)
53+
.filter(QualifiedExpression.class::isInstance)
54+
.map(QualifiedExpressionImpl.class::cast)
55+
.map(QualifiedExpression::symbol)
56+
.map(Symbol::fullyQualifiedName)
57+
.filter(S3_BUCKET_UNENCRYPTED_FQN::equals)
58+
.isPresent();
59+
}
60+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<p>Server-side encryption (SSE) encrypts an object (not the metadata) as it is written to disk (where the S3 bucket resides) and decrypts it as it is
2+
read from disk. This doesn’t change the way the objects are accessed, as long as the user has the necessary permissions, objects are retrieved as if
3+
they were unencrypted. Thus, SSE only helps in the event of disk thefts, improper disposals of disks and other attacks on the AWS infrastructure
4+
itself.</p>
5+
<p>There are three SSE options:</p>
6+
<ul>
7+
<li> Server-Side Encryption with Amazon S3-Managed Keys (SSE-S3)
8+
<ul>
9+
<li> AWS manages encryption keys and the encryption itself (with AES-256) on its own. </li>
10+
</ul> </li>
11+
<li> Server-Side Encryption with Customer Master Keys (CMKs) Stored in AWS Key Management Service (SSE-KMS)
12+
<ul>
13+
<li> AWS manages the encryption (AES-256) of objects and encryption keys provided by the AWS KMS service. </li>
14+
</ul> </li>
15+
<li> Server-Side Encryption with Customer-Provided Keys (SSE-C)
16+
<ul>
17+
<li> AWS manages only the encryption (AES-256) of objects with encryption keys provided by the customer. AWS doesn’t store the customer’s
18+
encryption keys. </li>
19+
</ul> </li>
20+
</ul>
21+
<h2>Ask Yourself Whether</h2>
22+
<ul>
23+
<li> The S3 bucket stores sensitive information. </li>
24+
<li> The infrastructure needs to comply to some regulations, like HIPAA or PCI DSS, and other standards. </li>
25+
</ul>
26+
<p>There is a risk if you answered yes to any of those questions.</p>
27+
<h2>Recommended Secure Coding Practices</h2>
28+
<p>It’s recommended to use SSE. Choosing the appropriate option depends on the level of control required for the management of encryption keys.</p>
29+
<h2>Sensitive Code Example</h2>
30+
<p>Server-side encryption is not used:</p>
31+
<pre>
32+
bucket = s3.Bucket(self,"MyUnencryptedBucket",
33+
encryption=s3.BucketEncryption.UNENCRYPTED # Sensitive
34+
)
35+
</pre>
36+
<p>The default value of <code>encryption</code> is <code>KMS</code> if <code>encryptionKey</code> is set. Otherwise, if both parameters are absent the
37+
bucket is unencrypted.</p>
38+
<h2>Compliant Solution</h2>
39+
<p>Server-side encryption with Amazon S3-Managed Keys is used:</p>
40+
<pre>
41+
bucket = s3.Bucket(self,"MyEncryptedBucket",
42+
encryption=s3.BucketEncryption.S3_MANAGED # Compliant
43+
)
44+
45+
# Alternatively with a KMS key managed by the user.
46+
47+
bucket = s3.Bucket(self,"MyEncryptedBucket",
48+
encryptionKey=access_key # Compliant
49+
)
50+
</pre>
51+
<h2>See</h2>
52+
<ul>
53+
<li> <a href="https://owasp.org/Top10/A04_2021-Insecure_Design/">OWASP Top 10 2021 Category A4</a> - Insecure Design </li>
54+
<li> <a href="https://owasp.org/Top10/A05_2021-Security_Misconfiguration/">OWASP Top 10 2021 Category A5</a> - Security Misconfiguration </li>
55+
<li> <a href="https://cwe.mitre.org/data/definitions/311">MITRE, CWE-311</a> - Missing Encryption of Sensitive Data </li>
56+
<li> <a href="https://www.owasp.org/index.php/Top_10-2017_A3-Sensitive_Data_Exposure">OWASP Top 10 2017 Category A3</a> - Sensitive Data Exposure
57+
</li>
58+
<li> <a href="https://www.owasp.org/index.php/Top_10-2017_A6-Security_Misconfiguration">OWASP Top 10 2017 Category A6</a> - Security
59+
Misconfiguration </li>
60+
<li> <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/serv-side-encryption.html">AWS documentation</a> - Protecting data using
61+
server-side encryption </li>
62+
<li> <a href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_s3.BucketEncryption.html">CDK documentation</a> - BucketEncryption class
63+
</li>
64+
</ul>
65+
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"title": "Disabling server-side encryption of S3 buckets is security-sensitive",
3+
"type": "SECURITY_HOTSPOT",
4+
"status": "ready",
5+
"tags": [
6+
"aws",
7+
"cwe",
8+
"owasp-a6",
9+
"owasp-a3"
10+
],
11+
"defaultSeverity": "Minor",
12+
"ruleSpecification": "RSPEC-6245",
13+
"sqKey": "S6245",
14+
"scope": "Main",
15+
"securityStandards": {
16+
"CWE": [
17+
311
18+
],
19+
"OWASP": [
20+
"A3",
21+
"A6"
22+
],
23+
"CIS": [
24+
"2.1.1"
25+
],
26+
"OWASP Top 10 2021": [
27+
"A4",
28+
"A5"
29+
]
30+
}
31+
}

python-checks/src/main/resources/org/sonar/l10n/py/rules/python/Sonar_way_profile.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@
152152
"S6002",
153153
"S6019",
154154
"S6035",
155+
"S6245",
155156
"S6252",
156157
"S6281",
157158
"S6323",
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2022 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.python.checks.cdk;
21+
22+
import org.junit.Test;
23+
import org.sonar.python.checks.utils.PythonCheckVerifier;
24+
25+
public class S3BucketServerEncryptionCheckTest {
26+
27+
@Test
28+
public void test() {
29+
PythonCheckVerifier.verify("src/test/resources/checks/cdk/s3BucketServerEncryption.py", new S3BucketServerEncryptionCheck());
30+
}
31+
32+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from aws_cdk import aws_s3 as s3
2+
3+
bucket = s3.Bucket(self,"MyUnencryptedBucket") # NonCompliant {{Omitting 'encryption' disables server-side encryption. Make sure it is safe here.}}
4+
# ^^^^^^^^^
5+
6+
bucket = s3.Bucket(self,"MyUnencryptedBucket", encryption=s3.BucketEncryption.UNENCRYPTED) # NonCompliant {{Objects in the bucket are not encrypted. Make sure it is safe here.}}
7+
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
8+
9+
bucket = s3.Bucket(self,"MyEncryptedBucket",
10+
encryption=s3.BucketEncryption.KMS_MANAGED # Compliant
11+
)
12+
bucket = s3.Bucket(self,"MyEncryptedBucket",
13+
encryption=s3.BucketEncryption.S3_MANAGED # Compliant
14+
)
15+
bucket = s3.Bucket(self,"MyEncryptedBucket",
16+
encryption=s3.BucketEncryption.KMS # Compliant
17+
)
18+
bucket = s3.Bucket(self,"MyEncryptedBucket",
19+
encryption_key=my_encryption_key # Compliant
20+
)
21+
22+
bucket = s3.Bucket(self,"MyUnencryptedBucket",
23+
encryption=s3.BucketEncryption.UNENCRYPTED, # NonCompliant {{Objects in the bucket are not encrypted. Make sure it is safe here.}}
24+
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
25+
encryption_key=my_encryption_key
26+
)
27+
28+
type_KMS = s3.BucketEncryption.KMS
29+
bucket = s3.Bucket(self,"MyEncryptedBucket",
30+
encryption=type_KMS # Compliant
31+
)
32+
33+
my_encryption_key2 = aws_cdk.aws_kms.IKey() # Compliant
34+
bucket = s3.Bucket(self,"MyEncryptedBucket",
35+
encryption=s3.BucketEncryption.S3_MANAGED,
36+
encryption_key=my_encryption_key2)
37+
38+
def create_bucket():
39+
type_unencrypted = s3.BucketEncryption.UNENCRYPTED
40+
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^> {{Propagated setting.}}
41+
bucket = s3.Bucket(self,"MyEncryptedBucket",
42+
encryption=type_unencrypted, # NonCompliant {{Objects in the bucket are not encrypted. Make sure it is safe here.}}
43+
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^
44+
encryption_key=my_encryption_key2)
45+
46+
47+
coverage = s3.Bucket(self, "bucket", encryption=unresolved_reference) # Compliant

0 commit comments

Comments
 (0)