CVE-2022-23506: Insertion of Sensitive Information into Log File (CWE-532)
value: Spinnaker is an open source, multi-cloud continuous delivery platform for releasing software changes, and Spinnaker's Rosco microservice produces machine images. Rosco prior to versions 1.29.2, 1.28.4, and 1.27.3 does not property mask secrets generated via packer builds.  This can lead to exposure of sensitive AWS credentials in packer log files. Versions 1.29.2, 1.28.4, and 1.27.3 of Rosco contain fixes for this issue.

A workaround is available. It's recommended to use short lived credentials via role assumption and IAM profiles. Additionally, credentials can be set in `/home/spinnaker/.aws/credentials` and `/home/spinnaker/.aws/config` as a volume mount for Rosco pods vs. setting credentials in roscos bake config properties. Last even with those it's recommend to use IAM Roles vs. long lived credentials. This drastically mitigates the risk of credentials exposure. If users have used static credentials, it's recommended to purge any bake logs for AWS, evaluate whether AWS_ACCESS_KEY, SECRET_KEY and/or other sensitive data has been introduced in log files and bake job logs. Then, rotate these credentials and evaluate potential improper use of those credentials.
CWE description: Information written to log files can be of a sensitive nature and give valuable guidance to an attacker or expose sensitive user information.
CVSS v3 base score: 7.5
Published: 2023-01-03T21:15Z
Repository: https://github.com/spinnaker/rosco
Language: Other
Files changed: rosco-core/src/main/groovy/com/netflix/spinnaker/rosco/providers/aws/AWSBakeHandler.groovy, rosco-core/src/main/groovy/com/netflix/spinnaker/rosco/providers/aws/config/RoscoAWSConfiguration.groovy, rosco-core/src/test/groovy/com/netflix/spinnaker/rosco/providers/aws/AWSBakeHandlerSpec.groovy, rosco-core/src/test/groovy/com/netflix/spinnaker/rosco/providers/util/LocalJobFriendlyPackerCommandFactorySpec.groovy
Commit date: 2022-12-08 12:50:39 -0800
Commit message: Merge pull request from GHSA-wqq8-664f-54hh

* test(core): verify that JobRequest.maskedTokenizedCommand really masks

* feat(core): add aws.bakery-defaults.maskedPackerParameters configuration parameter

with default value [ 'aws_access_key', 'aws_secret_key' ] to match what AWSBakeHandler uses and hide potentially secret information by default.
Diff: diff --git a/rosco-core/src/main/groovy/com/netflix/spinnaker/rosco/providers/aws/AWSBakeHandler.groovy b/rosco-core/src/main/groovy/com/netflix/spinnaker/rosco/providers/aws/AWSBakeHandler.groovy
index 20de73b..ff255d8 100644
--- a/rosco-core/src/main/groovy/com/netflix/spinnaker/rosco/providers/aws/AWSBakeHandler.groovy
+++ b/rosco-core/src/main/groovy/com/netflix/spinnaker/rosco/providers/aws/AWSBakeHandler.groovy
@@ -229,10 +229,15 @@ public class AWSBakeHandler extends CloudProviderBakeHandler {
     }
 
     return new Bake(id: bakeId, ami: amiId, image_name: imageName, artifacts: artifacts)
   }
 
+  @Override
+  List<String> getMaskedPackerParameters() {
+    return awsBakeryDefaults.maskedPackerParameters
+  }
+
   private String lookupAmiByName(String name, String region, String account, VmType vmType, boolean mostRecent) {
     def images = AuthenticatedRequest.allowAnonymous(
       {
         retrySupport.retry({
           clouddriverService.findAmazonImageByName(name, account, region)
@@ -245,9 +250,9 @@ public class AWSBakeHandler extends CloudProviderBakeHandler {
       def matchingImages = images?.findAll { it?.attributes?.virtualizationType == vmType }
       image = matchingImages?.max {it?.attributes?.creationDate }
     } else {
       image = images?.find { it.attributes.virtualizationType == vmType }
     }
-    
+
     return image?.amis?.get(region)?.first()
   }
 }
diff --git a/rosco-core/src/main/groovy/com/netflix/spinnaker/rosco/providers/aws/config/RoscoAWSConfiguration.groovy b/rosco-core/src/main/groovy/com/netflix/spinnaker/rosco/providers/aws/config/RoscoAWSConfiguration.groovy
index 4900ee6..95713a1 100644
--- a/rosco-core/src/main/groovy/com/netflix/spinnaker/rosco/providers/aws/config/RoscoAWSConfiguration.groovy
+++ b/rosco-core/src/main/groovy/com/netflix/spinnaker/rosco/providers/aws/config/RoscoAWSConfiguration.groovy
@@ -56,10 +56,11 @@ class RoscoAWSConfiguration {
     String awsVpcId
     Boolean awsAssociatePublicIpAddress
     String templateFile
     BakeRequest.VmType defaultVirtualizationType
     List<AWSOperatingSystemVirtualizationSettings> baseImages = []
+    List<String> maskedPackerParameters = [ 'aws_access_key', 'aws_secret_key' ]
   }
 
   static class AWSOperatingSystemVirtualizationSettings {
     BakeOptions.BaseImage baseImage
     List<AWSVirtualizationSettings> virtualizationSettings = []
diff --git a/rosco-core/src/test/groovy/com/netflix/spinnaker/rosco/providers/aws/AWSBakeHandlerSpec.groovy b/rosco-core/src/test/groovy/com/netflix/spinnaker/rosco/providers/aws/AWSBakeHandlerSpec.groovy
index 791fce1..c22feae 100644
--- a/rosco-core/src/test/groovy/com/netflix/spinnaker/rosco/providers/aws/AWSBakeHandlerSpec.groovy
+++ b/rosco-core/src/test/groovy/com/netflix/spinnaker/rosco/providers/aws/AWSBakeHandlerSpec.groovy
@@ -1439,9 +1439,35 @@ class AWSBakeHandlerSpec extends Specification implements TestDefaults {
       1 * imageNameFactoryMock.buildAppVersionStr(bakeRequest, osPackages, DEB_PACKAGE_TYPE) >> appVersionStr
       1 * imageNameFactoryMock.buildPackagesParameter(DEB_PACKAGE_TYPE, osPackages) >> fullyQualifiedPackageName
       1 * packerCommandFactoryMock.buildPackerCommand("", parameterMap, null, "$configDir/$awsBakeryDefaults.templateFile")
   }
 
+  void 'getMaskedPackerParameters returns the expected default'() {
+    setup:
+      @Subject
+      AWSBakeHandler awsBakeHandler = new AWSBakeHandler(awsBakeryDefaults: new RoscoAWSConfiguration.AWSBakeryDefaults())
+
+    when:
+      def maskedPackerParams = awsBakeHandler.maskedPackerParameters
+
+    then:
+      maskedPackerParams == [ 'aws_access_key', 'aws_secret_key' ]
+  }
+
+  void 'getMaskedPackerParameters returns the expected default'() {
+    setup:
+      def paramsToMask = [ 'foo' ]
+      @Subject
+      AWSBakeHandler awsBakeHandler = new AWSBakeHandler(awsBakeryDefaults: new RoscoAWSConfiguration.AWSBakeryDefaults(maskedPackerParameters: paramsToMask))
+
+
+    when:
+      def maskedPackerParams = awsBakeHandler.maskedPackerParameters
+
+    then:
+      maskedPackerParams == paramsToMask
+  }
+
   static class NoSleepRetry extends RetrySupport {
     void sleep(long time) {}
   }
 }
diff --git a/rosco-core/src/test/groovy/com/netflix/spinnaker/rosco/providers/util/LocalJobFriendlyPackerCommandFactorySpec.groovy b/rosco-core/src/test/groovy/com/netflix/spinnaker/rosco/providers/util/LocalJobFriendlyPackerCommandFactorySpec.groovy
index 718b306..df9963d 100644
--- a/rosco-core/src/test/groovy/com/netflix/spinnaker/rosco/providers/util/LocalJobFriendlyPackerCommandFactorySpec.groovy
+++ b/rosco-core/src/test/groovy/com/netflix/spinnaker/rosco/providers/util/LocalJobFriendlyPackerCommandFactorySpec.groovy
@@ -73,20 +73,21 @@ class LocalJobFriendlyPackerCommandFactorySpec extends Specification implements
     setup:
 
     when:
       def packerCommand = packerCommandFactory.buildPackerCommand("", parameterMap, null, "")
       def jobRequest = new JobRequest(tokenizedCommand: packerCommand, maskedParameters: maskedPackerParameters, jobId: SOME_UUID)
-      def commandLine = new CommandLine(jobRequest.tokenizedCommand[0])
-      def arguments = (String []) Arrays.copyOfRange(jobRequest.tokenizedCommand.toArray(), 1, jobRequest.tokenizedCommand.size())
+      def maskedTokenizedCommand = jobRequest.maskedTokenizedCommand
+      def commandLine = new CommandLine(maskedTokenizedCommand[0])
+      def arguments = (String []) Arrays.copyOfRange(maskedTokenizedCommand.toArray(), 1, maskedTokenizedCommand.size())
       commandLine.addArguments(arguments, false)
       def g = commandLine.toString()
       def cmdLineList =  commandLine.toStrings().toList()
 
-
     then:
       cmdLineList  == expectedCommandLine
 
     where:
-      parameterMap                          | maskedPackerParameters | expectedCommandLine
-      [packages: "package1 package2"]       | []                     | ["packer", "build", "-color=false", "-var", "packages=package1 package2"]
+      parameterMap                                              | maskedPackerParameters | expectedCommandLine
+      [packages: "package1 package2"]                           | []                     | ["packer", "build", "-color=false", "-var", "packages=package1 package2"]
+      [packages: "package1 package2", secret: "mysecret"]       | ["secret"]             | ["packer", "build", "-color=false", "-var", "packages=package1 package2", "-var", "secret=******"]
   }
 }

Vulnerable code:     
      def commandLine = new CommandLine(jobRequest.tokenizedCommand[0])
      def arguments = (String []) Arrays.copyOfRange(jobRequest.tokenizedCommand.toArray(), 1, jobRequest.tokenizedCommand.size())

      parameterMap                          | maskedPackerParameters | expectedCommandLine
      [packages: "package1 package2"]       | []                     | ["packer", "build", "-color=false", "-var", "packages=package1 package2"]
Fixed code:   @Override
  List<String> getMaskedPackerParameters() {
    return awsBakeryDefaults.maskedPackerParameters
  }


    List<String> maskedPackerParameters = [ 'aws_access_key', 'aws_secret_key' ]
  void 'getMaskedPackerParameters returns the expected default'() {
    setup:
      @Subject
      AWSBakeHandler awsBakeHandler = new AWSBakeHandler(awsBakeryDefaults: new RoscoAWSConfiguration.AWSBakeryDefaults())

    when:
      def maskedPackerParams = awsBakeHandler.maskedPackerParameters

    then:
      maskedPackerParams == [ 'aws_access_key', 'aws_secret_key' ]
  }

  void 'getMaskedPackerParameters returns the expected default'() {
    setup:
      def paramsToMask = [ 'foo' ]
      @Subject
      AWSBakeHandler awsBakeHandler = new AWSBakeHandler(awsBakeryDefaults: new RoscoAWSConfiguration.AWSBakeryDefaults(maskedPackerParameters: paramsToMask))


    when:
      def maskedPackerParams = awsBakeHandler.maskedPackerParameters

    then:
      maskedPackerParams == paramsToMask
  }

      def maskedTokenizedCommand = jobRequest.maskedTokenizedCommand
      def commandLine = new CommandLine(maskedTokenizedCommand[0])
      def arguments = (String []) Arrays.copyOfRange(maskedTokenizedCommand.toArray(), 1, maskedTokenizedCommand.size())
      parameterMap                                              | maskedPackerParameters | expectedCommandLine
      [packages: "package1 package2"]                           | []                     | ["packer", "build", "-color=false", "-var", "packages=package1 package2"]
      [packages: "package1 package2", secret: "mysecret"]       | ["secret"]             | ["packer", "build", "-color=false", "-var", "packages=package1 package2", "-var", "secret=******"]