Skip to content

Commit 2fc0d8b

Browse files
Merge branch 'main' into external-plugin-application-class-loader
2 parents e255dca + 84e8f8b commit 2fc0d8b

File tree

16 files changed

+360
-24
lines changed

16 files changed

+360
-24
lines changed

CHANGELOG.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ endif::[]
3333
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.
3434
* 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]
3535
* Added support for overwritting the service version per classloader - {pull}1726[#1726]
36+
* Support for the Java LDAP client - {pull}2355[#2355]
3637
* Added support for setting the application class loader when starting a transaction via the public api - {pull}2369[#2369]
3738
3839
[float]

Jenkinsfile

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ pipeline {
177177
expression { return env.GITHUB_COMMENT?.contains('integration tests') }
178178
expression { matchesPrLabel(label: 'ci:agent-integration') }
179179
expression { return env.CHANGE_ID != null && !pullRequest.draft }
180+
not { changeRequest() }
180181
}
181182
}
182183
steps {
@@ -206,6 +207,7 @@ pipeline {
206207
expression { return env.GITHUB_COMMENT?.contains('integration tests') }
207208
expression { matchesPrLabel(label: 'ci:agent-integration') }
208209
expression { return env.CHANGE_ID != null && !pullRequest.draft }
210+
not { changeRequest() }
209211
}
210212
}
211213
steps {
@@ -237,11 +239,9 @@ pipeline {
237239
}
238240
when {
239241
beforeAgent true
240-
allOf {
241-
anyOf {
242-
branch 'main'
243-
expression { return env.GITHUB_COMMENT?.contains('benchmark tests') }
244-
}
242+
anyOf {
243+
branch 'main'
244+
expression { return env.GITHUB_COMMENT?.contains('benchmark tests') }
245245
expression { return params.bench_ci }
246246
}
247247
}
@@ -271,10 +271,6 @@ pipeline {
271271
stage('Javadoc') {
272272
agent { label 'linux && immutable' }
273273
options { skipDefaultCheckout() }
274-
when {
275-
beforeAgent true
276-
expression { return env.ONLY_DOCS == "false" }
277-
}
278274
steps {
279275
withGithubNotify(context: 'Javadoc') {
280276
deleteDir()
@@ -302,6 +298,7 @@ pipeline {
302298
expression { return env.GITHUB_COMMENT?.contains('end-to-end tests') }
303299
expression { matchesPrLabel(label: 'ci:end-to-end') }
304300
expression { return env.CHANGE_ID != null && !pullRequest.draft }
301+
not { changeRequest() }
305302
}
306303
}
307304
}

apm-agent-core/src/test/resources/json-specs/span_types.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,12 @@
225225
"ruby",
226226
"java"
227227
]
228+
},
229+
"ldap": {
230+
"__description": "LDAP client",
231+
"__used_by": [
232+
"java"
233+
]
228234
}
229235
}
230236
},
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<parent>
7+
<artifactId>apm-agent-plugins</artifactId>
8+
<groupId>co.elastic.apm</groupId>
9+
<version>1.28.5-SNAPSHOT</version>
10+
</parent>
11+
12+
<artifactId>apm-java-ldap-plugin</artifactId>
13+
<name>${project.groupId}:${project.artifactId}</name>
14+
15+
<properties>
16+
<apm-agent-parent.base.dir>${project.basedir}/../..</apm-agent-parent.base.dir>
17+
<animal.sniffer.skip>true</animal.sniffer.skip>
18+
</properties>
19+
20+
<dependencies>
21+
<dependency>
22+
<groupId>com.unboundid</groupId>
23+
<artifactId>unboundid-ldapsdk</artifactId>
24+
<version>6.0.3</version>
25+
<scope>test</scope>
26+
</dependency>
27+
</dependencies>
28+
</project>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package co.elastic.apm.agent.java_ldap;
20+
21+
import co.elastic.apm.agent.impl.ElasticApmTracer;
22+
import co.elastic.apm.agent.impl.GlobalTracer;
23+
import co.elastic.apm.agent.impl.transaction.AbstractSpan;
24+
import co.elastic.apm.agent.impl.transaction.Outcome;
25+
import co.elastic.apm.agent.impl.transaction.Span;
26+
import com.sun.jndi.ldap.Connection;
27+
import com.sun.jndi.ldap.LdapResult;
28+
import net.bytebuddy.asm.Advice;
29+
import net.bytebuddy.implementation.bytecode.assign.Assigner;
30+
31+
import javax.annotation.Nullable;
32+
33+
public class LdapClientAdvice {
34+
35+
private static final ElasticApmTracer tracer = GlobalTracer.requireTracerImpl();
36+
37+
@Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
38+
public static Object onEnter(@Advice.Origin("#m") String methodName, @Advice.FieldValue(value = "conn", typing = Assigner.Typing.DYNAMIC) Connection connection) {
39+
AbstractSpan<?> parent = tracer.getActive();
40+
if (parent == null) {
41+
return null;
42+
}
43+
44+
Span span = parent.createExitSpan();
45+
if (span == null) {
46+
return null;
47+
}
48+
49+
span.appendToName("LDAP ").appendToName(methodName)
50+
.withType("external")
51+
.withSubtype("ldap");
52+
53+
if (connection != null) {
54+
span.getContext()
55+
.getDestination().withAddress(connection.host).withPort(connection.port)
56+
.getService().getResource().append(connection.host).append(":").append(connection.port);
57+
}
58+
59+
return span.activate();
60+
}
61+
62+
@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
63+
public static void onExit(@Advice.Enter @Nullable Object spanObj, @Nullable @Advice.Return LdapResult ldapResult, @Nullable @Advice.Thrown Throwable t) {
64+
Span span = (Span) spanObj;
65+
if (span != null) {
66+
span.withOutcome((ldapResult != null && ldapResult.status == 0 /* LDAP_SUCCESS */) ? Outcome.SUCCESS : Outcome.FAILURE)
67+
.captureException(t)
68+
.deactivate().end();
69+
}
70+
}
71+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package co.elastic.apm.agent.java_ldap;
20+
21+
import co.elastic.apm.agent.bci.TracerAwareInstrumentation;
22+
import net.bytebuddy.description.method.MethodDescription;
23+
import net.bytebuddy.description.type.TypeDescription;
24+
import net.bytebuddy.matcher.ElementMatcher;
25+
26+
import java.util.Collection;
27+
import java.util.Collections;
28+
29+
import static net.bytebuddy.matcher.ElementMatchers.named;
30+
31+
public class LdapClientInstrumentation extends TracerAwareInstrumentation {
32+
33+
@Override
34+
public ElementMatcher<? super TypeDescription> getTypeMatcher() {
35+
return named("com.sun.jndi.ldap.LdapClient");
36+
}
37+
38+
@Override
39+
public ElementMatcher<? super MethodDescription> getMethodMatcher() {
40+
return named("authenticate")
41+
.or(named("add"))
42+
.or(named("compare"))
43+
.or(named("delete"))
44+
.or(named("extendedOp"))
45+
.or(named("moddn"))
46+
.or(named("modify"))
47+
.or(named("search"));
48+
}
49+
50+
@Override
51+
public String getAdviceClassName() {
52+
return "co.elastic.apm.agent.java_ldap.LdapClientAdvice";
53+
}
54+
55+
@Override
56+
public Collection<String> getInstrumentationGroupNames() {
57+
return Collections.singletonList("java-ldap");
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
co.elastic.apm.agent.java_ldap.LdapClientInstrumentation
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package co.elastic.apm.agent.java_ldap;
20+
21+
import co.elastic.apm.agent.AbstractInstrumentationTest;
22+
import co.elastic.apm.agent.impl.transaction.Outcome;
23+
import co.elastic.apm.agent.impl.transaction.Span;
24+
import co.elastic.apm.agent.impl.transaction.Transaction;
25+
import co.elastic.apm.agent.testutils.TestPort;
26+
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
27+
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
28+
import com.unboundid.ldap.listener.InMemoryListenerConfig;
29+
import com.unboundid.ldif.LDIFReader;
30+
import org.junit.jupiter.api.AfterAll;
31+
import org.junit.jupiter.api.BeforeAll;
32+
import org.junit.jupiter.api.Test;
33+
34+
import javax.naming.Context;
35+
import javax.naming.directory.InitialDirContext;
36+
import java.util.Hashtable;
37+
import java.util.List;
38+
39+
import static org.assertj.core.api.Assertions.assertThat;
40+
41+
public class LdapClientAdviceTest extends AbstractInstrumentationTest {
42+
43+
private static InMemoryDirectoryServer ldapServer;
44+
45+
@BeforeAll
46+
static void startServer() throws Exception {
47+
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=example,dc=com");
48+
config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("test", TestPort.getAvailableRandomPort()));
49+
50+
ldapServer = new InMemoryDirectoryServer(config);
51+
ldapServer.importFromLDIF(true, new LDIFReader(LdapClientAdviceTest.class.getResourceAsStream("/test.ldif")));
52+
ldapServer.startListening();
53+
}
54+
55+
@AfterAll
56+
static void stopServer() {
57+
ldapServer.shutDown(true);
58+
}
59+
60+
@Test
61+
void testSuccessfulAuthentication() throws Exception {
62+
Hashtable<String, String> environment = getEnvironment();
63+
64+
Transaction transaction = startTestRootTransaction();
65+
try {
66+
new InitialDirContext(environment).close();
67+
} catch (Exception ignored) {
68+
} finally {
69+
transaction.deactivate().end();
70+
}
71+
72+
List<Span> spans = reporter.getSpans();
73+
assertThat(spans.size()).isEqualTo(1);
74+
75+
assertSpan(spans.get(0), "authenticate", Outcome.SUCCESS);
76+
}
77+
78+
@Test
79+
void testUnsuccessfulAuthentication() {
80+
Hashtable<String, String> environment = getEnvironment();
81+
environment.put(Context.SECURITY_CREDENTIALS, "wrong password");
82+
83+
Transaction transaction = startTestRootTransaction();
84+
try {
85+
new InitialDirContext(environment).close();
86+
} catch (Exception ignored) {
87+
ignored.printStackTrace();
88+
} finally {
89+
transaction.deactivate().end();
90+
}
91+
92+
List<Span> spans = reporter.getSpans();
93+
assertThat(spans.size()).isEqualTo(1);
94+
95+
assertSpan(spans.get(0), "authenticate", Outcome.FAILURE);
96+
}
97+
98+
@Test
99+
void testSearch() {
100+
Hashtable<String, String> environment = getEnvironment();
101+
102+
Transaction transaction = startTestRootTransaction();
103+
try {
104+
InitialDirContext context = new InitialDirContext(environment);
105+
context.search("dc=example,dc=com", "(&(objectClass=person)(uid=tobiasstadler))", null);
106+
context.close();
107+
} catch (Exception ignored) {
108+
} finally {
109+
transaction.deactivate().end();
110+
}
111+
112+
List<Span> spans = reporter.getSpans();
113+
assertThat(spans.size()).isEqualTo(2);
114+
115+
assertSpan(spans.get(0), "authenticate", Outcome.SUCCESS);
116+
assertSpan(spans.get(1), "search", Outcome.SUCCESS);
117+
}
118+
119+
private static Hashtable<String, String> getEnvironment() {
120+
Hashtable<String, String> environment = new Hashtable<>();
121+
122+
environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
123+
environment.put(Context.PROVIDER_URL, "ldap://localhost:" + ldapServer.getListenPort());
124+
environment.put(Context.SECURITY_AUTHENTICATION, "simple");
125+
environment.put(Context.SECURITY_PRINCIPAL, "cn=Tobias Stadler,ou=Users,dc=example,dc=com");
126+
environment.put(Context.SECURITY_CREDENTIALS, "123456");
127+
128+
return environment;
129+
}
130+
131+
static void assertSpan(Span span, String method, Outcome outcome) {
132+
assertThat(span.getNameAsString()).isEqualTo("LDAP " + method);
133+
assertThat(span.getType()).isEqualTo("external");
134+
assertThat(span.getSubtype()).isEqualTo("ldap");
135+
assertThat(span.getOutcome()).isEqualTo(outcome);
136+
assertThat(span.getContext().getDestination().getAddress().toString()).isEqualTo("localhost");
137+
assertThat(span.getContext().getDestination().getPort()).isEqualTo(ldapServer.getListenPort());
138+
}
139+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
dn: dc=example,dc=com
2+
objectClass: domain
3+
objectClass: top
4+
dc: example
5+
6+
dn: ou=Users,dc=example,dc=com
7+
objectClass: organizationalUnit
8+
objectClass: top
9+
ou: Users
10+
11+
dn: ou=Groups,dc=example,dc=com
12+
objectClass: organizationalUnit
13+
objectClass: top
14+
ou: Groups
15+
16+
dn: cn=Tobias Stadler,ou=Users,dc=example,dc=com
17+
objectClass: inetOrgPerson
18+
objectClass: organizationalPerson
19+
objectClass: person
20+
objectClass: top
21+
cn: Tobias Stadler
22+
sn: Stadler
23+
uid: tobiasstadler
24+
userPassword: 123456

0 commit comments

Comments
 (0)