Skip to content

Commit

Permalink
Merge branch 'main' into external-plugin-application-class-loader
Browse files Browse the repository at this point in the history
  • Loading branch information
tobiasstadler authored Jan 28, 2022
2 parents e255dca + 84e8f8b commit 2fc0d8b
Show file tree
Hide file tree
Showing 16 changed files with 360 additions and 24 deletions.
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]
* Added support for setting the application class loader when starting a transaction via the public api - {pull}2369[#2369]
[float]
Expand Down
15 changes: 6 additions & 9 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ pipeline {
expression { return env.GITHUB_COMMENT?.contains('integration tests') }
expression { matchesPrLabel(label: 'ci:agent-integration') }
expression { return env.CHANGE_ID != null && !pullRequest.draft }
not { changeRequest() }
}
}
steps {
Expand Down Expand Up @@ -206,6 +207,7 @@ pipeline {
expression { return env.GITHUB_COMMENT?.contains('integration tests') }
expression { matchesPrLabel(label: 'ci:agent-integration') }
expression { return env.CHANGE_ID != null && !pullRequest.draft }
not { changeRequest() }
}
}
steps {
Expand Down Expand Up @@ -237,11 +239,9 @@ pipeline {
}
when {
beforeAgent true
allOf {
anyOf {
branch 'main'
expression { return env.GITHUB_COMMENT?.contains('benchmark tests') }
}
anyOf {
branch 'main'
expression { return env.GITHUB_COMMENT?.contains('benchmark tests') }
expression { return params.bench_ci }
}
}
Expand Down Expand Up @@ -271,10 +271,6 @@ pipeline {
stage('Javadoc') {
agent { label 'linux && immutable' }
options { skipDefaultCheckout() }
when {
beforeAgent true
expression { return env.ONLY_DOCS == "false" }
}
steps {
withGithubNotify(context: 'Javadoc') {
deleteDir()
Expand Down Expand Up @@ -302,6 +298,7 @@ pipeline {
expression { return env.GITHUB_COMMENT?.contains('end-to-end tests') }
expression { matchesPrLabel(label: 'ci:end-to-end') }
expression { return env.CHANGE_ID != null && !pullRequest.draft }
not { changeRequest() }
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions apm-agent-core/src/test/resources/json-specs/span_types.json
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;
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
Loading

0 comments on commit 2fc0d8b

Please sign in to comment.