javajibyguardmaven-jib

Integrating Google jib with yGuard


Is there a way to obfuscate exploded/packaged output of jib-maven-plugin with yGuard (or some other obfuscator)?

I can think of a way using other tools such as exec-maven-plugin + jib cli.

Another possible way can be to devise a 3rd party jib-extension or even fork/hack jib-maven-plugin all together.

Maybe someone can share their experience with that.

For context I am trying to ship a Spring Boot application build using Maven and AntRun for yGuard.


Solution

  • I managed to figure it out myself.

    Here is my exec-maven-plug + jib cli solution for anyone out there.

    To test paste it, adapt it to your environment, and run mvn clean package -P local.

    This pom.xml is from a multi-module setup, so you may have to refactor or omit the <parent></parent> tag to suit you needs.

    What it does:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
    
      <parent>
        <artifactId>redacted</artifactId>
        <groupId>com.redacted</groupId>
        <version>0.0.1</version>
      </parent>
    
      <artifactId>sm-test</artifactId>
      <version>0.0.1</version>
      <name>test</name>
      <description>test</description>
      <packaging>jar</packaging>
    
      <properties>
        <profile.name>default</profile.name>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <mainClass>com.redacted.smtest.SmTestApplication</mainClass>
        <java.server.user>0:0</java.server.user>
        <java.to.image.tag>ghcr.io/redacted/${project.artifactId}:${project.version}-${profile.name}</java.to.image.tag>
        <java.from.image.tag>openjdk:11.0.14-jre@sha256:e2e90ec68d3eee5a526603a3160de353a178c80b05926f83d2f77db1d3440826</java.from.image.tag>
        <java.from.classpath>../target/${project.name}/${profile.name}/${project.build.finalName}.jar</java.from.classpath>
      </properties>
    
      <profiles>
    
        <!-- local -->
        <profile>
          <id>local</id>
          <properties>
            <profile.name>local</profile.name>
          </properties>
          <activation>
            <property>
              <name>noTest</name>
              <value>true</value>
            </property>
          </activation>
          <build>
            <plugins>
              <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                  <skipTests>true</skipTests>
                </configuration>
              </plugin>
              <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <executions>
                  <execution>
                    <id>jib jar</id>
                    <phase>package</phase>
                    <goals>
                      <goal>exec</goal>
                    </goals>
                    <configuration>
                      <executable>jib</executable>
                      <workingDirectory>.</workingDirectory>
                      <skip>false</skip>
                      <arguments>
                        <argument>jar</argument>
                        <argument>--mode=exploded</argument>
                        <argument>--target=${java.to.image.tag}</argument>
                        <argument>--from=${java.from.image.tag}</argument>
                        <argument>--user=${java.server.user}</argument>
                        <argument>--creation-time=${maven.build.timestamp}</argument>
                        <argument>--jvm-flags=-Xms32m,-Xmx128m,-Dspring.profiles.active=default</argument>
                        <argument>${java.from.classpath}</argument>
                      </arguments>
                    </configuration>
                  </execution>
                  <execution>
                    <id>docker pull</id>
                    <phase>install</phase>
                    <goals>
                      <goal>exec</goal>
                    </goals>
                    <configuration>
                      <executable>docker</executable>
                      <workingDirectory>.</workingDirectory>
                      <skip>false</skip>
                      <arguments>
                        <argument>pull</argument>
                        <argument>${java.to.image.tag}</argument>
                      </arguments>
                    </configuration>
                  </execution>
                </executions>
              </plugin>
            </plugins>
          </build>
        </profile>
    
      </profiles>
    
      <dependencies>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-configuration-processor</artifactId>
          <optional>true</optional>
        </dependency>
        <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <optional>true</optional>
        </dependency>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
        </dependency>
        <dependency>
          <groupId>com.yworks</groupId>
          <artifactId>yguard</artifactId>
          <scope>compile</scope>
        </dependency>
      </dependencies>
    
      <build>
        <finalName>${project.name}</finalName>
        <directory>../target/${project.name}/${profile.name}/</directory>
        <outputDirectory>../target/${project.name}/${profile.name}/classes</outputDirectory>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-antrun-plugin</artifactId>
            <executions>
              <execution>
                <id>obfuscate</id>
                <phase>compile</phase>
                <goals>
                  <goal>run</goal>
                </goals>
                <configuration>
                  <skip>false</skip>
                  <tasks>
    
                    <property name="runtime_classpath" refid="maven.runtime.classpath"/>
                    <!--suppress UnresolvedMavenProperty -->
                    <taskdef name="yguard" classname="com.yworks.yguard.YGuardTask" classpath="${runtime_classpath}"/>
    
                    <mkdir dir="${project.build.directory}/obfuscated"/>
    
                    <yguard>
    
                      <inoutpair in="${project.build.directory}/classes"
                                 out="${project.build.directory}/obfuscated"/>
    
                      <externalclasses>
                        <!--suppress UnresolvedMavenProperty -->
                        <pathelement path="${runtime_classpath}"/>
                      </externalclasses>
    
                      <rename mainclass="${mainClass}"
                              logfile="${project.build.directory}/rename.log.xml"
                              scramble="true"
                              replaceClassNameStrings="true">
    
                        <property name="error-checking" value="pedantic"/>
                        <property name="naming-scheme" value="best"/>
                        <property name="language-conformity" value="compatible"/>
                        <property name="overload-enabled" value="true"/>
    
                        <!-- Generated by sm-test -->
                        <map>
                          <class map="d1e6064d$5a15$449b$a632$b2d967a61021" name="com.redacted.smtest.YGuardMappingRunner"/>
                        </map>
    
                      </rename>
    
                    </yguard>
    
                    <delete dir="${project.build.directory}/classes/com"/>
    
                    <copy todir="${project.build.directory}/classes/com/" overwrite="true">
                      <fileset dir="${project.build.directory}/obfuscated/com/" includes="**"/>
                    </copy>
    
                    <delete dir="${project.build.directory}/obfuscated"/>
    
                  </tasks>
                </configuration>
              </execution>
            </executions>
          </plugin>
          <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
              <mainClass>${mainClass}</mainClass>
              <excludes>
                <exclude>
                  <groupId>org.projectlombok</groupId>
                  <artifactId>lombok</artifactId>
                </exclude>
              </excludes>
            </configuration>
          </plugin>
        </plugins>
      </build>
    
    </project>
    

    Here is an associate jib.yaml file for jib cli to work:

    apiVersion: jib/v1alpha1
    kind: BuildFile
    
    workingDirectory: "/app"
    
    entrypoint: ["java","-cp","/app/resources:/app/classes:/app/libs/*"]
    
    layers:
      entries:
        - name: classes
          files:
            - properties:
                filePermissions: 755
              src: /classes
              dest: /app/classes
    

    I also had to write a small throw-away class to generate custom mapping for unique class and package names for yGuard (apparently it cannot manage to do it on its own):

    package com.redacted.smtest;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.stereotype.Component;
    
    import java.io.IOException;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.util.UUID;
    
    @Slf4j
    @Component
    public class YGuardMappingRunner implements CommandLineRunner {
      @Override
      public void run(String... args) throws Exception {
        if (args == null || args.length == 0)
          return;
    
        generateYGuardMapping(Path.of(args[0]));
      }
    
      void generateYGuardMapping(Path path) throws IOException {
        var packageSb = new StringBuilder();
        var classSb = new StringBuilder();
    
        mapPackages(path, packageSb);
        mapClasses(path, classSb);
    
        if (packageSb.length() > 0 && classSb.length() > 0)
          log.info(
              "\n<!-- Generated by sm-test -->\n<map>\n{}\n{}</map>",
              packageSb.toString(),
              classSb.toString());
        else if (packageSb.length() > 0 && classSb.length() == 0)
          log.info("\n<!-- Generated by sm-test -->\n<map>\n{}</map>", packageSb.toString());
        else if (packageSb.length() == 0 && classSb.length() > 0)
          log.info("\n<!-- Generated by sm-test -->\n<map>\n{}</map>", classSb.toString());
      }
    
      private void mapClasses(Path path, StringBuilder classSb) throws IOException {
        try (var stream = Files.walk(path, Integer.MAX_VALUE)) {
          stream
              .distinct()
              .filter(o -> o.getNameCount() >= 12)
              .filter(Files::isRegularFile)
              .map(o -> o.subpath(8, o.getNameCount()))
              .map(o -> o.toString().replace("\\", ".").replace(".java", ""))
              .filter(o -> !o.contains("Sm"))
              .sorted()
              .forEach(
                  o ->
                      classSb.append(
                          String.format("%2s<class map=\"%s\" name=\"%s\"/>%n", "", getRandStr(), o)));
        }
      }
    
      private void mapPackages(Path path, StringBuilder packageSb) throws IOException {
        try (var stream = Files.walk(path, Integer.MAX_VALUE)) {
          stream
              .map(Path::getParent)
              .distinct()
              .filter(o -> o.getNameCount() >= 12)
              .map(o -> o.subpath(8, o.getNameCount()))
              .map(o -> o.toString().replace("\\", "."))
              .sorted()
              .forEach(
                  o ->
                      packageSb.append(
                          String.format(
                              "%2s<package map=\"%s\" name=\"%s\"/>%n", "", getRandStr(), o)));
        }
      }
    
      private String getRandStr() {
        return UUID.randomUUID().toString().replaceAll("[-]+", "\\$");
      }
    }