Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Health processor with tls #58

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 21 additions & 17 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,46 +8,49 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-java@v1
- uses: actions/setup-java@v4
with:
java-version: 11
distribution: 'temurin'
java-version: 17
- name: Test
run: ./gradlew test
run: ./gradlew test --info
- name: Upload reports
if: ${{ failure() }}
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: reports-test
path: alfresco-health-processor-platform/build/reports
- name: Upload analysis to sonarcloud
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
GITHUB_TOKEN: ${{ github.token }}
run: ./gradlew aggregateJacocoReport sonarqube
run: ./gradlew aggregateJacocoReport sonarqube --info
- name: Javadoc
run: ./gradlew javadoc
run: ./gradlew javadoc --info
integration-test:
runs-on: ubuntu-latest
if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' || startsWith(github.ref, 'refs/tags/') }}
strategy:
matrix:
flavour: [ "community" ]
version: [ "52", "61", "62", "70" ]
version: [ "71", "71-ssl", "72", "72-ssl", "74", "74-ssl" ]
#add SSL and secret here.
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-java@v1
- uses: actions/setup-java@v4
with:
java-version: 11
distribution: 'temurin'
java-version: 17
- name: Test
run: ./gradlew :integration-tests:alfresco-${{ matrix.flavour }}-${{ matrix.version }}:integrationTest -Prandom_ports=true
run: ./gradlew :integration-tests:alfresco-${{ matrix.flavour }}-${{ matrix.version }}:integrationTest -Prandom_ports=true --info
- name: Upload reports
if: ${{ failure() }}
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: reports-integration-test-${{ matrix.flavour }}-${{ matrix.version }}
path: integration-tests/alfresco-${{ matrix.flavour }}-${{ matrix.version }}/build/reports
Expand All @@ -56,16 +59,17 @@ jobs:
runs-on: ubuntu-latest
if: ${{ startsWith(github.ref, 'refs/tags/') }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-java@v1
- uses: actions/setup-java@v4
with:
java-version: 11
distribution: 'temurin'
java-version: 17
- name: Publish
env:
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.MAVEN_CENTRAL_GPG_KEY }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.MAVEN_CENTRAL_GPG_PASSWORD }}
ORG_GRADLE_PROJECT_sonatype_username: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_sonatype_password: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
run: ./gradlew publish -PsigningKeyId=CDE3528F
run: ./gradlew publish -PsigningKeyId=CDE3528F --info
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ Please consult the official Alfresco documentation on how to install Module Pack

### Supported Alfresco versions

The module is systematically integration tested against Alfresco 5.2, 6.1, 6.2 and 7.0.
The module is systematically integration tested against Alfresco 7.1, 7.2, 7.4
Please use a previous version of the health-processor for older versions of alfresco.


## Overview

Expand Down Expand Up @@ -167,13 +169,18 @@ eu.xenit.alfresco.healthprocessor.plugin.content-validation.properties=cm:conten
If this property is not set (which is the default), the plugin will request all properties of type `d:content`
from Alfresco's `DictionaryService`.

When validating content, "NONE" in the reporting means, there is no status for a certain document, because it was not checked.
For example, content checks report nodes without any content property as none.

#### Solr index Validation

Activation property: `eu.xenit.alfresco.healthprocessor.plugin.solr-index.enabled=true`

Validates that nodes are present in a Solr/Alfresco Search Services index.

By default, the plugin will check the solr server configured with `solr.host` & `solr.port` with the default `alfresco` and `archive` indexes.
> [!WARNING]
> when ussing ssl this needs to be explicitly set to use HTTPS and the solr.port.ssl

It is possible to configure the solr servers to check and, for sharded setups, which nodes should be present in which solr server.

Expand Down Expand Up @@ -286,7 +293,11 @@ Activation property: `eu.xenit.alfresco.healthprocessor.reporter.log.summary.ena
A simple implementation that writes, once a Health Processor cycle is completed, a summary and unhealthy nodes to the
Alfresco logs.

Relevant logger: `log4j.logger.eu.xenit.alfresco.healthprocessor.reporter.log.SummaryLoggingHealthReporter=INFO`
> [!WARNING]
> Starting from alfresco 7.4, alfresco has migrated to log4j2. The original log4j logger will no longer exist.

Relevant logger (log4j) (pre Alfresco 7.3): `log4j.logger.eu.xenit.alfresco.healthprocessor.reporter.log.SummaryLoggingHealthReporter=INFO`
Relevant logger (log4j2): `logger.eu-xenit-alfresco-healthprocessor.name=eu.xenit.alfresco.healthprocessor`

Example output:

Expand Down
4 changes: 4 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# TODO

* fix alfresco 23
* add Javax libs for alfred-telemetry (or update alfred telemetry)
4 changes: 2 additions & 2 deletions alfresco-health-processor-api/build.gradle
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
plugins {
id 'java-library'
id 'maven-publish'
id 'io.freefair.lombok' version '6.2.0'
id 'io.freefair.javadocs' version '6.2.0'
id 'io.freefair.lombok'
id 'io.freefair.javadocs' version '8.10'
}

description = "Java API for extensions for ${rootProject.description}"
Expand Down
18 changes: 9 additions & 9 deletions alfresco-health-processor-platform/build.gradle
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
plugins {
id "com.github.johnrengelman.shadow" version "7.1.0"
id "com.github.johnrengelman.shadow" version "8.1.1"
id 'java-test-fixtures'
id 'java-library'
id 'io.freefair.lombok' version '6.2.0'
id "io.freefair.lombok"
id 'eu.xenit.alfresco'
id 'eu.xenit.amp'
}
Expand Down Expand Up @@ -51,6 +51,9 @@ dependencies {

ampLib project(":alfresco-health-processor-platform:alfresco-7-upwards")

ampLib "commons-dbcp:commons-dbcp:1.4"
ampLib "org.apache.commons:commons-dbcp2:2.9.0"

testCompileOnly "org.projectlombok:lombok:${lombokVersion}"
testAnnotationProcessor "org.projectlombok:lombok:${lombokVersion}"

Expand All @@ -67,17 +70,14 @@ dependencies {
testFixturesApi("org.alfresco:alfresco-data-model")
}

import com.github.jengelman.gradle.plugins.shadow.tasks.ConfigureShadowRelocation

task relocateShadowJar(type: ConfigureShadowRelocation) {
target = tasks.shadowJar
prefix = "eu.xenit.alfresco.healthprocessor.internal.shadow"
}
//relocateshadowjar Code was changed from (pre 8.10) please see https://github.com/GradleUp/shadow/blob/main/src/docs/plugins/README.md#automatic-package-relocation-with-shadow-prior-to-v810
//Configuration is now added directly in the shadowJar tasks instead of (the now failing) importing ConfigureShadowRelocation

tasks.shadowJar.dependsOn tasks.relocateShadowJar
tasks.assemble.dependsOn tasks.shadowJar

shadowJar {
enableRelocation true
relocationPrefix = "eu.xenit.alfresco.healthprocessor.internal.shadow"
configurations = [ project.configurations.embedded ]
archiveClassifier.set('')
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
logger.eu-xenit-alfresco-healthprocessor.name=eu.xenit.alfresco.healthprocessor
logger.eu-xenit-alfresco-healthprocessor.level=INFO
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,14 @@

<bean id="eu.xenit.alfresco.healthprocessor.plugins.solr.SolrRequestExecutor"
class="eu.xenit.alfresco.healthprocessor.plugins.solr.SolrRequestExecutor" >
<constructor-arg name="checkTransaction" value="${eu.xenit.alfresco.healthprocessor.plugin.solr-index.check-transaction}" />
<constructor-arg name="checkTransaction" value="${eu.xenit.alfresco.healthprocessor.plugin.solr-index.check-transaction}" />
<constructor-arg name="globalProperties" ref="global-properties" />
<constructor-arg name="clientFactory" ref="eu.xenit.alfresco.healthprocessor.plugins.solr.utils.HealthProcessorSimpleHttpClientFactory" />
</bean>

<bean id="eu.xenit.alfresco.healthprocessor.plugins.solr.utils.HealthProcessorSimpleHttpClientFactory"
class="eu.xenit.alfresco.healthprocessor.plugins.solr.utils.HealthProcessorSimpleHttpClientFactory" >
<constructor-arg name="globalProperties" ref="global-properties" />
</bean>

<bean id="eu.xenit.alfresco.healthprocessor.plugins.solr.endpoint.SearchEndpointSelector"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,106 @@

import com.fasterxml.jackson.databind.JsonNode;
import eu.xenit.alfresco.healthprocessor.plugins.solr.endpoint.SearchEndpoint;

import java.io.IOException;
import java.nio.file.Files;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Collection;
import java.util.Map;
import java.util.Properties;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import javax.net.ssl.SSLContext;

import eu.xenit.alfresco.healthprocessor.plugins.solr.utils.HealthProcessorSimpleHttpClientFactory;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import org.alfresco.service.cmr.repository.NodeRef.Status;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.ssl.SSLContexts;
import org.springframework.util.ResourceUtils;


/**
* Performs HTTP requests on a {@link SearchEndpoint}
*/
@Slf4j
@RequiredArgsConstructor
public class SolrRequestExecutor {



private Properties globalProperties;
private final HttpClient httpClient;
private final boolean checkTransaction;

public SolrRequestExecutor(Boolean checkTransaction) {
this(HttpClientBuilder.create().build(), checkTransaction);
public SolrRequestExecutor(Boolean checkTransaction, Properties globalProperties, HealthProcessorSimpleHttpClientFactory clientFactory
) {
this.globalProperties = globalProperties;
log.info("Creating httpclient");
this.httpClient = clientFactory.createHttpClient();
this.checkTransaction = checkTransaction;
}

public SolrRequestExecutor(HttpClient httpClient, Boolean checkTransaction) {
this.httpClient = httpClient;
this.checkTransaction = checkTransaction;
}


/*
private HttpClient buildHttpClient() {
if (Boolean.parseBoolean(globalProperties.getProperty(GLOBAL_PROPERTY_SOLRREQUESTEXECUTOR_USE_SSL))) {
log.debug("Creating sll httpclient");
log.warn("- keystore location: {}", globalProperties.getProperty(GLOBAL_PROPERTY_KEYSTORE_FILE_LOCATION));
log.warn("- keystore type: {}", globalProperties.getProperty(GLOBAL_PROPERTY_KEYSTORE_TYPE));
log.warn("- keystore pas: {}", globalProperties.getProperty(GLOBAL_PROPERTY_KEYSTORE_PASSWORD));

log.warn("- truststore location: {}",
globalProperties.getProperty(GLOBAL_PROPERTY_TRUSTSTORE_FILE_LOCATION));
log.warn("- truststore type: {}", globalProperties.getProperty(GLOBAL_PROPERTY_TRUSTSTORE_TYPE));
log.warn("- trustore pas: {}", globalProperties.getProperty(GLOBAL_PROPERTY_TRUSTSTORE_PASSWORD));


//TODO do we add a default value here? In case props are not present?
HttpClientBuilder httpClientBuilder = HttpClientBuilder
.create()
.setSSLContext(getAlfrescoSolrSslContext(
globalProperties.getProperty(GLOBAL_PROPERTY_KEYSTORE_TYPE),
globalProperties.getProperty(GLOBAL_PROPERTY_KEYSTORE_PASSWORD),
globalProperties.getProperty(GLOBAL_PROPERTY_KEYSTORE_FILE_LOCATION),
globalProperties.getProperty(GLOBAL_PROPERTY_TRUSTSTORE_TYPE),
globalProperties.getProperty(GLOBAL_PROPERTY_TRUSTSTORE_PASSWORD),
globalProperties.getProperty(GLOBAL_PROPERTY_TRUSTSTORE_FILE_LOCATION)
));

//By Default, the hostname is checked as well
if (Boolean.parseBoolean(globalProperties.getProperty(GLOBAL_PROPERTY_HEALTHPROCESSOR_VALIDATE_HOSTNAMES, "true"))) {
log.warn("MTLS host validation disabled.");
httpClientBuilder.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE);
}

return httpClientBuilder
.build();
}
return HttpClientBuilder.create().build();
}
*/




/**
* Performs a search operation on an endpoint to determine if the nodes are indexed or not
Expand Down Expand Up @@ -116,7 +186,7 @@ private JsonNode executeSearchRequest(SearchEndpoint endpoint, Collection<Status
// Searching for both DBID and TX from Alfresco validates that the node is indexed
// and that it's related transaction is the latest. (making sure no later transaction was accidentally skipped)
solrQuery = nodeStatuses.stream()
.map(status -> "(DBID:" + status.getDbId() + "%20AND%20INTXID:" + status.getDbTxnId() + ")" )
.map(status -> "(DBID:" + status.getDbId() + "%20AND%20INTXID:" + status.getDbTxnId() + ")")
.collect(Collectors.joining("%20OR%20"));
}

Expand All @@ -131,20 +201,22 @@ private JsonNode executeSearchRequest(SearchEndpoint endpoint, Collection<Status
}

/**
* Schedules an async SolrNodeCommand for a node on a search endpoint.
* This action/command is scheduled for execution by solr or a failure is returned.
* @param endpoint the search endpoint
* Schedules an async SolrNodeCommand for a node on a search endpoint. This action/command is scheduled for
* execution by solr or a failure is returned.
*
* @param endpoint the search endpoint
* @param nodeStatus node status containing information about the dbIDs and transactionIds
* @param command Solr action that will be executed
* @return
* @param command Solr action that will be executed
* @throws IOException when the command can not be sent to solr
*/
public SolrActionResponse executeAsyncNodeCommand(SearchEndpoint endpoint, Status nodeStatus, SolrNodeCommand command)
public SolrActionResponse executeAsyncNodeCommand(SearchEndpoint endpoint, Status nodeStatus,
SolrNodeCommand command)
throws IOException {
String coreName = endpoint.getCoreName();

String solrEndpoint = "cores?action=" + command.getCommand() + "&wt=json&coreName=" + coreName;
solrEndpoint += (command.isTargetsTransaction())? "&txid=" + nodeStatus.getDbTxnId() : "&nodeid=" + nodeStatus.getDbId();
solrEndpoint += (command.isTargetsTransaction()) ? "&txid=" + nodeStatus.getDbTxnId()
: "&nodeid=" + nodeStatus.getDbId();

HttpUriRequest indexRequest = new HttpGet(endpoint.getAdminUri().resolve(solrEndpoint));

Expand All @@ -169,14 +241,15 @@ private SolrActionResponse parseActionWebResponse(JsonNode response, String core

@Value
public static class SolrActionResponse {

private final boolean successFull;
private final String message;
}

/**
* The boolean targetsTransaction indicates if the action should be sent for the transaction the node was contained in.
* If true, the nodeCommand will be scheduled for the complete transaction of this node.
* If false, the nodeCommand is scheduled for this single node contained in the nodestatus.
* The boolean targetsTransaction indicates if the action should be sent for the transaction the node was contained
* in. If true, the nodeCommand will be scheduled for the complete transaction of this node. If false, the
* nodeCommand is scheduled for this single node contained in the nodestatus.
*/
@AllArgsConstructor
public enum SolrNodeCommand {
Expand Down
Loading
Loading