Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ endif::[]
Note: this may change your service names if you relied on the auto-discovery that uses the name of the jar file. If that jar file also contains an `Implementation-Title` attribute in the `MANIFEST.MF` file, the latter will take precedence.
* When the `MANIFEST.MF` of the main jar contains the `Implementation-Version` attribute, it is used as the default service version (except for application servers) - {pull}1922[#1922]
* Added support for overwritting the service version per classloader - {pull}1726[#1726]
* Support for the Java LDAP client - {pull}2355[#2355]

[float]
===== Bug fixes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,12 @@
"ruby",
"java"
]
},
"ldap": {
"__description": "LDAP client",
"__used_by": [
"java"
]
}
}
},
Expand Down
28 changes: 28 additions & 0 deletions apm-agent-plugins/apm-java-ldap-plugin/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?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>apm-agent-plugins</artifactId>
<groupId>co.elastic.apm</groupId>
<version>1.28.5-SNAPSHOT</version>
</parent>

<artifactId>apm-java-ldap-plugin</artifactId>
<name>${project.groupId}:${project.artifactId}</name>

<properties>
<apm-agent-parent.base.dir>${project.basedir}/../..</apm-agent-parent.base.dir>
<animal.sniffer.skip>true</animal.sniffer.skip>
</properties>

<dependencies>
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>6.0.3</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package co.elastic.apm.agent.java_ldap;

import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.GlobalTracer;
import co.elastic.apm.agent.impl.transaction.AbstractSpan;
import co.elastic.apm.agent.impl.transaction.Outcome;
import co.elastic.apm.agent.impl.transaction.Span;
import com.sun.jndi.ldap.Connection;
import com.sun.jndi.ldap.LdapResult;
Comment on lines +26 to +27
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like IntelliJ has issues with the imports from the java.naming/com.sun.jndi.ldap module.

Screen Shot 2022-01-28 at 13 52 13

Applying the suggestion only works until the maven project is reloaded.

I've tried adding

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <compilerArgs>
                        <arg>--add-exports</arg>
                        <arg>java.naming/com.sun.jndi.ldap=ALL-UNNAMED</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
        </plugins>
    </build>

But that yields this error: java: exporting a package from system module java.naming is not allowed with --release

@tobiasstadler Did you manage to get this work in an IDE?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please try adding <maven.compiler.source>1.7</maven.compiler.source> to the properties of the plugin pom?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem probably is that the IntelliJ profile in the parent pom sets maven.compiler.target and maven.compiler.target to 11.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, right. This works. However, only with a Java 11 compiler. With Java 17 there are still a few issues. Under Preferences > Build, Execution, Deployment > Compiler > Java Compiler, I had to uncheck the Use '--release' option for cross-compilation option. But even after doing that, the tests fail with an IllegalAccessError:

java.lang.IllegalAccessError: class co.elastic.apm.agent.java_ldap.LdapClientAdvice (in unnamed module @0x25be445f) cannot access class com.sun.jndi.ldap.Connection (in module java.naming) because module java.naming does not export com.sun.jndi.ldap to unnamed module @0x25be445f
	at co.elastic.apm.agent.java_ldap.LdapClientAdvice.onEnter(LdapClientAdvice.java:55)
	at java.naming/com.sun.jndi.ldap.LdapClient.authenticate(LdapClient.java:153)
    ...

This will fail the Java 17 compatibility tests and it's probably also an issue in production with Java 17.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just tested it with Java17. It compiles fine but the tests are failing with the error you mentioned.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import net.bytebuddy.asm.Advice;
import net.bytebuddy.implementation.bytecode.assign.Assigner;

import javax.annotation.Nullable;

public class LdapClientAdvice {

private static final ElasticApmTracer tracer = GlobalTracer.requireTracerImpl();

@Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static Object onEnter(@Advice.Origin("#m") String methodName, @Advice.FieldValue(value = "conn", typing = Assigner.Typing.DYNAMIC) Connection connection) {
AbstractSpan<?> parent = tracer.getActive();
if (parent == null) {
return null;
}

Span span = parent.createExitSpan();
if (span == null) {
return null;
}

span.appendToName("LDAP ").appendToName(methodName)
.withType("external")
.withSubtype("ldap");

if (connection != null) {
span.getContext()
.getDestination().withAddress(connection.host).withPort(connection.port)
.getService().getResource().append(connection.host).append(":").append(connection.port);
}

return span.activate();
}

@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
public static void onExit(@Advice.Enter @Nullable Object spanObj, @Nullable @Advice.Return LdapResult ldapResult, @Nullable @Advice.Thrown Throwable t) {
Span span = (Span) spanObj;
if (span != null) {
span.withOutcome((ldapResult != null && ldapResult.status == 0 /* LDAP_SUCCESS */) ? Outcome.SUCCESS : Outcome.FAILURE)
.captureException(t)
.deactivate().end();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package co.elastic.apm.agent.java_ldap;

import co.elastic.apm.agent.bci.TracerAwareInstrumentation;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

import java.util.Collection;
import java.util.Collections;

import static net.bytebuddy.matcher.ElementMatchers.named;

public class LdapClientInstrumentation extends TracerAwareInstrumentation {

@Override
public ElementMatcher<? super TypeDescription> getTypeMatcher() {
return named("com.sun.jndi.ldap.LdapClient");
}

@Override
public ElementMatcher<? super MethodDescription> getMethodMatcher() {
return named("authenticate")
.or(named("add"))
.or(named("compare"))
.or(named("delete"))
.or(named("extendedOp"))
.or(named("moddn"))
.or(named("modify"))
.or(named("search"));
}

@Override
public String getAdviceClassName() {
return "co.elastic.apm.agent.java_ldap.LdapClientAdvice";
}

@Override
public Collection<String> getInstrumentationGroupNames() {
return Collections.singletonList("java-ldap");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
co.elastic.apm.agent.java_ldap.LdapClientInstrumentation
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package co.elastic.apm.agent.java_ldap;

import co.elastic.apm.agent.AbstractInstrumentationTest;
import co.elastic.apm.agent.impl.transaction.Outcome;
import co.elastic.apm.agent.impl.transaction.Span;
import co.elastic.apm.agent.impl.transaction.Transaction;
import co.elastic.apm.agent.testutils.TestPort;
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldif.LDIFReader;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import javax.naming.Context;
import javax.naming.directory.InitialDirContext;
import java.util.Hashtable;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

public class LdapClientAdviceTest extends AbstractInstrumentationTest {

private static InMemoryDirectoryServer ldapServer;

@BeforeAll
static void startServer() throws Exception {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=example,dc=com");
config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("test", TestPort.getAvailableRandomPort()));

ldapServer = new InMemoryDirectoryServer(config);
ldapServer.importFromLDIF(true, new LDIFReader(LdapClientAdviceTest.class.getResourceAsStream("/test.ldif")));
ldapServer.startListening();
}

@AfterAll
static void stopServer() {
ldapServer.shutDown(true);
}

@Test
void testSuccessfulAuthentication() throws Exception {
Hashtable<String, String> environment = getEnvironment();

Transaction transaction = startTestRootTransaction();
try {
new InitialDirContext(environment).close();
} catch (Exception ignored) {
} finally {
transaction.deactivate().end();
}

List<Span> spans = reporter.getSpans();
assertThat(spans.size()).isEqualTo(1);

assertSpan(spans.get(0), "authenticate", Outcome.SUCCESS);
}

@Test
void testUnsuccessfulAuthentication() {
Hashtable<String, String> environment = getEnvironment();
environment.put(Context.SECURITY_CREDENTIALS, "wrong password");

Transaction transaction = startTestRootTransaction();
try {
new InitialDirContext(environment).close();
} catch (Exception ignored) {
ignored.printStackTrace();
} finally {
transaction.deactivate().end();
}

List<Span> spans = reporter.getSpans();
assertThat(spans.size()).isEqualTo(1);

assertSpan(spans.get(0), "authenticate", Outcome.FAILURE);
}

@Test
void testSearch() {
Hashtable<String, String> environment = getEnvironment();

Transaction transaction = startTestRootTransaction();
try {
InitialDirContext context = new InitialDirContext(environment);
context.search("dc=example,dc=com", "(&(objectClass=person)(uid=tobiasstadler))", null);
context.close();
} catch (Exception ignored) {
} finally {
transaction.deactivate().end();
}

List<Span> spans = reporter.getSpans();
assertThat(spans.size()).isEqualTo(2);

assertSpan(spans.get(0), "authenticate", Outcome.SUCCESS);
assertSpan(spans.get(1), "search", Outcome.SUCCESS);
}

private static Hashtable<String, String> getEnvironment() {
Hashtable<String, String> environment = new Hashtable<>();

environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
environment.put(Context.PROVIDER_URL, "ldap://localhost:" + ldapServer.getListenPort());
environment.put(Context.SECURITY_AUTHENTICATION, "simple");
environment.put(Context.SECURITY_PRINCIPAL, "cn=Tobias Stadler,ou=Users,dc=example,dc=com");
environment.put(Context.SECURITY_CREDENTIALS, "123456");

return environment;
}

static void assertSpan(Span span, String method, Outcome outcome) {
assertThat(span.getNameAsString()).isEqualTo("LDAP " + method);
assertThat(span.getType()).isEqualTo("external");
assertThat(span.getSubtype()).isEqualTo("ldap");
assertThat(span.getOutcome()).isEqualTo(outcome);
assertThat(span.getContext().getDestination().getAddress().toString()).isEqualTo("localhost");
assertThat(span.getContext().getDestination().getPort()).isEqualTo(ldapServer.getListenPort());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
dn: dc=example,dc=com
objectClass: domain
objectClass: top
dc: example

dn: ou=Users,dc=example,dc=com
objectClass: organizationalUnit
objectClass: top
ou: Users

dn: ou=Groups,dc=example,dc=com
objectClass: organizationalUnit
objectClass: top
ou: Groups

dn: cn=Tobias Stadler,ou=Users,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
cn: Tobias Stadler
sn: Stadler
uid: tobiasstadler
userPassword: 123456
1 change: 1 addition & 0 deletions apm-agent-plugins/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
<module>apm-jaxws-plugin-jakartaee-test</module>
<module>apm-jaxrs-plugin-jakartaee-test</module>
<module>apm-scheduled-annotation-plugin-jakartaee-test</module>
<module>apm-java-ldap-plugin</module>
</modules>

<dependencies>
Expand Down
5 changes: 5 additions & 0 deletions apm-agent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,11 @@
<artifactId>apm-awslambda-plugin</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>apm-java-ldap-plugin</artifactId>
<version>${project.version}</version>
</dependency>

<!-- For auto-generating configuration docs -->
<dependency>
Expand Down
Loading