Skip to content

Commit

Permalink
deconflicting tests -- separating zarf build into its own test
Browse files Browse the repository at this point in the history
  • Loading branch information
ablanchard committed Apr 2, 2024
2 parents 970d3f5 + 47651d9 commit a28e989
Show file tree
Hide file tree
Showing 19 changed files with 2,310 additions and 5 deletions.
39 changes: 38 additions & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ concurrency:
cancel-in-progress: true

jobs:
title_check:
docker_build:
runs-on: ubuntu-latest
name: Test Docker Build
permissions:
Expand All @@ -27,6 +27,43 @@ jobs:

- name: Test building the docker image
run: uds run dev-build

zarf_build:
runs-on: ubuntu-latest
name: Test Zarf Build
permissions:
pull-requests: read
contents: read

steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

- name: Environment setup
uses: defenseunicorns/uds-common/.github/actions/setup@fc12e3a773580020a1d63e254525eab0f8b99fc8

- name: Test building a zarf package
run: uds run build-zarf-pkg

plugin_unit_tests:
runs-on: ubuntu-latest
name: Keycloak Plugin Unit Tests
permissions:
pull-requests: read
contents: read

steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

- name: Environment setup
uses: defenseunicorns/uds-common/.github/actions/setup@fc12e3a773580020a1d63e254525eab0f8b99fc8

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'

- name: Plugin Unit Tests
run: mvn -B package --file src/plugin/pom.xml
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ This repo builds the UDS Identity (Keycloak) Config image used by UDS Identity.
| dev-build | Build the image locally for dev |
| dev-update-image | Build the image and import locally into k3d |
| dev-theme | Copy theme to Keycloak in dev cluster |
| dev-plugin | Build and run unit tests for keycloak plugin|
| cacert | Get the CA cert value for the Istio Gateway |
| debug-istio-traffic | Debug Istio traffic on keycloak |
| regenerate-test-pki | Generate a PKI cert for testing |
Expand Down
49 changes: 49 additions & 0 deletions docs/PLUGIN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Plugin Development

The Keycloak plugin is a custom plugin that is used for x509 certificate enrollment.

## Requirements

Working on the plugin requires JDK17+ and Maven 3.5+.

```bash
# local java version
java -version

# loval maven version
mvn -version
```

## Plugin Testing with Keycloak

After making changes to the plugin code and verifying that unit tests are passing ( and hopefully writing some more ), test against Keycloak.

See the `New uds-identity-config Image` section in the [CUSTOMIZE.md](./CUSTOMIZE.md#new-uds-identity-config-image) for building, publishing, and using the new image with `uds-core`.

## Plugin Unit Testing / Code Coverage

The maven surefire plugin is configured in the [pom.xml](./src/plugin/pom.xml). Some important commands that can be used when developing/testing on the plugin:

> [!IMPORTANT]
> `mvn` commands will need to be executed from inside of the `src/plugin` directory
|Command|Description|
|-------|-----------|
| `mvn clean install` | Cleans up build artifacts and then builds and installs project into local maven repository. |
| `mvn clean test` | Cleans up build artifacts and then compiles the source code and runs all tests in the project. |
| `mvn clean test -Dtest=com.defenseunicorns.uds.keycloak.plugin.X509ToolsTest` | Same as `mvn clean test` but instead of running all tests in project, only runs the tests in designated file. |
| `mvn surefire-report:report` | This command will run the `mvn clean test` and then generate the surefire-report.html file in `target/site` |

### Viewing the Maven Surefire Reports

```bash
# maven command from src/plugin directory
mvn surefire-report:report

# uds command from base directory
uds run dev-plugin
```

Open the `src/plugin/target/site/index.html` file in your browser to view the test coverage. This will hot reload each time the site folder is rebuilt.

Sometimes IDE's won't allow opening files in a browser, either download an extension for managing this or open it from file explorer.
42 changes: 39 additions & 3 deletions src/plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,33 @@
<artifactId>keycloak-services</artifactId>
<version>${keycloak.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-crypto-default</artifactId>
<version>${keycloak.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpg-fips</artifactId>
<version>1.0.7.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand All @@ -58,9 +80,23 @@
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<argLine>
--add-opens=java.base/java.io=ALL-UNNAMED
--add-opens=java.base/java.lang=ALL-UNNAMED
--add-opens=java.base/java.util=ALL-UNNAMED
--add-opens=java.base/java.util.regex=ALL-UNNAMED
--add-opens=java.base/java.base=ALL-UNNAMED
--add-opens=java.base/java.util.stream=ALL-UNNAMED
--add-opens=java.base/java.net=ALL-UNNAMED
--add-opens=java.base/sun.security.jca=ALL-UNNAMED
</argLine>
</configuration>
</plugin>
</plugins>
</build>
</project>



Original file line number Diff line number Diff line change
Expand Up @@ -208,5 +208,4 @@ private static Object getX509Identity(final KeycloakSession session, final HttpR
}
return null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package com.defenseunicorns.uds.keycloak.plugin;

import org.apache.commons.io.FilenameUtils;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import org.keycloak.authentication.FormContext;
import org.keycloak.http.HttpRequest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.keycloak.authentication.ValidationContext;
import org.keycloak.common.crypto.UserIdentityExtractor;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel;
import org.keycloak.authentication.authenticators.x509.X509ClientCertificateAuthenticator;
import org.keycloak.models.*;
import org.keycloak.services.validation.Validation;
import org.keycloak.services.x509.X509ClientCertificateLookup;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.sessions.RootAuthenticationSessionModel;
import org.mockito.Mock;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.yaml.snakeyaml.Yaml;

import com.defenseunicorns.uds.keycloak.plugin.utils.CommonConfig;
import com.defenseunicorns.uds.keycloak.plugin.utils.NewObjectProvider;
import com.defenseunicorns.uds.keycloak.plugin.utils.UserModelDefaultMethodsImpl;
import com.defenseunicorns.uds.keycloak.plugin.utils.Utils;

import java.io.File;
import java.io.FileInputStream;
import java.security.GeneralSecurityException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

import static com.defenseunicorns.uds.keycloak.plugin.utils.Utils.setupFileMocks;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.powermock.api.mockito.PowerMockito.mockStatic;


@RunWith(PowerMockRunner.class)
@PrepareForTest({ Yaml.class, FileInputStream.class, File.class,
CommonConfig.class, FilenameUtils.class, NewObjectProvider.class,
X509Tools.class,
})
@PowerMockIgnore("javax.management.*")
class RegistrationValidation2Test {

@Mock
KeycloakSession keycloakSession;
@Mock
KeycloakContext keycloakContext;
@Mock
AuthenticationSessionModel authenticationSessionModel;
@Mock
RootAuthenticationSessionModel rootAuthenticationSessionModel;
@Mock
HttpRequest httpRequest;
@Mock
RealmModel realmModel;
@Mock
ValidationContext validationContext;
@Mock
X509ClientCertificateLookup x509ClientCertificateLookup;
@Mock
AuthenticatorConfigModel authenticatorConfigModel;
@Mock
X509ClientCertificateAuthenticator x509ClientCertificateAuthenticator;
@Mock
UserIdentityExtractor userIdentityExtractor;
@Mock
UserProvider userProvider;
@Mock
UserModel userModel;
@Mock
GroupProvider groupProvider;

public RegistrationValidation2Test() {}

@Before
public void setupMockBehavior() throws Exception {

setupFileMocks();

// common mock implementations
PowerMockito.when(validationContext.getSession()).thenReturn(keycloakSession);
PowerMockito.when(keycloakSession.getContext()).thenReturn(keycloakContext);
PowerMockito.when(keycloakSession.getContext().getAuthenticationSession()).thenReturn(authenticationSessionModel);
PowerMockito.when(authenticationSessionModel.getParentSession()).thenReturn(rootAuthenticationSessionModel);
PowerMockito.when(rootAuthenticationSessionModel.getId()).thenReturn("xxx");
PowerMockito.when(validationContext.getHttpRequest()).thenReturn(httpRequest);
PowerMockito.when(validationContext.getRealm()).thenReturn(realmModel);
PowerMockito.when(keycloakSession.groups()).thenReturn(groupProvider);

// setup X509Tools
PowerMockito.when(keycloakSession.getProvider(X509ClientCertificateLookup.class)).thenReturn(x509ClientCertificateLookup);

// create cert array and add the cert
X509Certificate[] certList = new X509Certificate[1];
X509Certificate x509Certificate2 = Utils.buildTestCertificate();
certList[0] = x509Certificate2;
PowerMockito.when(x509ClientCertificateLookup.getCertificateChain(httpRequest)).thenReturn(certList);

PowerMockito.when(realmModel.getAuthenticatorConfigsStream()).thenAnswer((stream) -> {
return Stream.of(authenticatorConfigModel);
});

// create map
Map<String, String> mapSting = new HashMap<>();
mapSting.put("x509-cert-auth.mapper-selection.user-attribute-name", "test");
PowerMockito.when(authenticatorConfigModel.getConfig()).thenReturn(mapSting);

PowerMockito.when(x509ClientCertificateAuthenticator
.getUserIdentityExtractor(any(X509AuthenticatorConfigModel.class))).thenReturn(userIdentityExtractor);
PowerMockito.when(keycloakSession.users()).thenReturn(userProvider);
PowerMockito.when(userProvider.searchForUserByUserAttributeStream(any(RealmModel.class), anyString(), anyString()))
.thenAnswer((stream) -> {
return Stream.of(userModel);
});

CryptoIntegration.init(this.getClass().getClassLoader());
}

@Test
public void testSuccess() {

mockStatic(X509Tools.class);

PowerMockito.when(X509Tools.getX509Username(any(FormContext.class))).thenReturn("something");

UserModelDefaultMethodsImpl userModelDefaultMethodsImpl = new UserModelDefaultMethodsImpl();
PowerMockito.when(validationContext.getUser()).thenReturn(userModelDefaultMethodsImpl);
PowerMockito.when(validationContext.getRealm()).thenReturn(realmModel);

MultivaluedMapImpl<String, String> formData = new MultivaluedMapImpl<>();
formData.add(Validation.FIELD_EMAIL, "test.user@test.bad");

PowerMockito.when(validationContext.getHttpRequest().getDecodedFormParameters()).thenReturn(formData);

RegistrationValidation registrationValidation = new RegistrationValidation();
registrationValidation.success(validationContext);
}

@Test
public void testSuccessNoX509() throws GeneralSecurityException {

// force no cert
PowerMockito.when(x509ClientCertificateLookup.getCertificateChain(httpRequest)).thenReturn(null);

UserModelDefaultMethodsImpl userModelDefaultMethodsImpl = new UserModelDefaultMethodsImpl();
PowerMockito.when(validationContext.getUser()).thenReturn(userModelDefaultMethodsImpl);
PowerMockito.when(validationContext.getRealm()).thenReturn(realmModel);

MultivaluedMapImpl<String, String> formData = new MultivaluedMapImpl<>();
formData.add(Validation.FIELD_EMAIL, "test.user@test.bad");

PowerMockito.when(validationContext.getHttpRequest().getDecodedFormParameters()).thenReturn(formData);

RegistrationValidation registrationValidation = new RegistrationValidation();
registrationValidation.success(validationContext);
}
}
Loading

0 comments on commit a28e989

Please sign in to comment.