Skip to content

Commit 6c9c755

Browse files
committed
Initial support for Kerberos auth
This commit adds - A new enum `ProxyAuthScheme` that enumerates the proxy auth mechanisms supported by Netty - `ProxyAuthGenerator` (internal) that knows how to generate the auth params for its respective auth scheme - `NegotiateProxyAuthGenerator` for Kerberos
1 parent f294ac6 commit 6c9c755

7 files changed

Lines changed: 336 additions & 0 deletions

File tree

bom-internal/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,12 @@
519519
<type>pom</type>
520520
<scope>import</scope>
521521
</dependency>
522+
<dependency>
523+
<groupId>org.apache.kerby</groupId>
524+
<artifactId>kerb-simplekdc</artifactId>
525+
<version>${kerb-simplekdc.version}</version>
526+
<scope>test</scope>
527+
</dependency>
522528
</dependencies>
523529
</dependencyManagement>
524530

http-clients/netty-nio-client/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,11 @@
233233
<artifactId>jetty-util</artifactId>
234234
<scope>test</scope>
235235
</dependency>
236+
<dependency>
237+
<groupId>org.apache.kerby</groupId>
238+
<artifactId>kerb-simplekdc</artifactId>
239+
<scope>test</scope>
240+
</dependency>
236241
</dependencies>
237242

238243
<dependencyManagement>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.http.nio.netty;
17+
18+
import software.amazon.awssdk.annotations.SdkPublicApi;
19+
20+
/**
21+
* Supported auth schemes for authentication with a proxy.
22+
*/
23+
@SdkPublicApi
24+
public enum ProxyAuthScheme {
25+
/**
26+
* Basic authentication.
27+
*/
28+
BASIC("Basic"),
29+
30+
/**
31+
* Kerberos authentication.
32+
*/
33+
NEGOTIATE("Negotiate"),
34+
;
35+
36+
private final String value;
37+
38+
ProxyAuthScheme(String value) {
39+
this.value = value;
40+
}
41+
42+
public String value() {
43+
return value;
44+
}
45+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.http.nio.netty.internal;
17+
18+
import com.sun.security.auth.module.Krb5LoginModule;
19+
import java.net.URI;
20+
import java.security.PrivilegedActionException;
21+
import java.security.PrivilegedExceptionAction;
22+
import java.util.HashMap;
23+
import java.util.Map;
24+
import javax.security.auth.Subject;
25+
import javax.security.auth.login.AppConfigurationEntry;
26+
import javax.security.auth.login.Configuration;
27+
import javax.security.auth.login.LoginContext;
28+
import javax.security.auth.login.LoginException;
29+
import org.ietf.jgss.GSSContext;
30+
import org.ietf.jgss.GSSException;
31+
import org.ietf.jgss.GSSManager;
32+
import org.ietf.jgss.GSSName;
33+
import org.ietf.jgss.Oid;
34+
import software.amazon.awssdk.annotations.SdkInternalApi;
35+
import software.amazon.awssdk.annotations.SdkTestInternalApi;
36+
import software.amazon.awssdk.http.SdkHttpRequest;
37+
import software.amazon.awssdk.http.nio.netty.ProxyAuthScheme;
38+
import software.amazon.awssdk.utils.BinaryUtils;
39+
40+
/**
41+
* Auth generator for Kerberos. This does not login/authentication to Kerberos. It expects the ticket cache to be present and
42+
* simply reads that to generate the token.
43+
*/
44+
@SdkInternalApi
45+
public class NegotiateProxyAuthGenerator implements ProxyAuthGenerator {
46+
private static final String OID = "1.3.6.1.5.5.2";
47+
private static final String SERVICE_NAME = "HTTP";
48+
private final Configuration config;
49+
50+
public NegotiateProxyAuthGenerator() {
51+
this(createDefaultConfig());
52+
}
53+
54+
@SdkTestInternalApi
55+
NegotiateProxyAuthGenerator(Configuration config) {
56+
this.config = config;
57+
}
58+
59+
@Override
60+
public ProxyAuthScheme scheme() {
61+
return ProxyAuthScheme.NEGOTIATE;
62+
}
63+
64+
@Override
65+
public String generateAuthParams(SdkHttpRequest request) {
66+
try {
67+
Subject subject = getSubject();
68+
69+
byte[] token = Subject.doAs(subject, (PrivilegedExceptionAction<byte[]>) () -> {
70+
GSSContext ctx = createGSSContext(getManager(), request.getUri());
71+
ctx.requestMutualAuth(true);
72+
return ctx.initSecContext(new byte[0], 0, 0);
73+
});
74+
75+
return BinaryUtils.toBase64(token);
76+
} catch (PrivilegedActionException e) {
77+
throw new RuntimeException("Unable to generate token", e);
78+
}
79+
}
80+
81+
private Subject getSubject() {
82+
try {
83+
LoginContext loginContext = new LoginContext("dummy", null, null, config);
84+
loginContext.login();
85+
return loginContext.getSubject();
86+
} catch (LoginException e) {
87+
throw new RuntimeException("Unable to perform login", e);
88+
}
89+
}
90+
91+
private GSSContext createGSSContext(GSSManager manager, URI endpoint) {
92+
try {
93+
String name = String.format("%s@%s", SERVICE_NAME, endpoint.getHost());
94+
GSSName serverName = manager.createName(name, GSSName.NT_HOSTBASED_SERVICE);
95+
Oid spnegoOid = new Oid(OID);
96+
return manager.createContext(serverName, spnegoOid, null,
97+
GSSContext.DEFAULT_LIFETIME);
98+
} catch (GSSException e) {
99+
throw new RuntimeException("Unable to create GSSContext", e);
100+
}
101+
}
102+
103+
private static GSSManager getManager() {
104+
return GSSManager.getInstance();
105+
}
106+
107+
/**
108+
* Create a generic {@link Configuration} that instructs the Kerberos login module to simply look in the ticket cache, and
109+
* not to prompt for passwords.
110+
* <p>
111+
* See javadoc for {@link Krb5LoginModule} for additional info on the configuration options.
112+
*/
113+
private static Configuration createDefaultConfig() {
114+
return new Configuration() {
115+
@Override
116+
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
117+
Map<String, String> opts = new HashMap<>();
118+
opts.put("useTicketCache", "true");
119+
opts.put("doNotPrompt", "true");
120+
return new AppConfigurationEntry[] {
121+
new AppConfigurationEntry(
122+
"com.sun.security.auth.module.Krb5LoginModule",
123+
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, opts)
124+
};
125+
}
126+
};
127+
}
128+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.http.nio.netty.internal;
17+
18+
import software.amazon.awssdk.annotations.SdkInternalApi;
19+
import software.amazon.awssdk.http.SdkHttpRequest;
20+
import software.amazon.awssdk.http.nio.netty.ProxyAuthScheme;
21+
22+
/**
23+
* Generates the auth params for an {@code Authorization} HTTP header.
24+
*/
25+
@SdkInternalApi
26+
public interface ProxyAuthGenerator {
27+
/**
28+
* The name of the auth scheme this generator supports.
29+
*/
30+
ProxyAuthScheme scheme();
31+
32+
/**
33+
* Generate the auth params for this request.
34+
*/
35+
String generateAuthParams(SdkHttpRequest request);
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.http.nio.netty.internal;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
20+
import java.io.IOException;
21+
import java.net.InetSocketAddress;
22+
import java.net.Socket;
23+
import java.nio.file.Files;
24+
import java.nio.file.Path;
25+
import java.util.HashMap;
26+
import java.util.Map;
27+
import javax.security.auth.login.AppConfigurationEntry;
28+
import javax.security.auth.login.Configuration;
29+
import org.apache.kerby.kerberos.kerb.KrbException;
30+
import org.apache.kerby.kerberos.kerb.client.KrbClient;
31+
import org.apache.kerby.kerberos.kerb.server.SimpleKdcServer;
32+
import org.apache.kerby.kerberos.kerb.type.ticket.TgtTicket;
33+
import org.junit.jupiter.api.AfterAll;
34+
import org.junit.jupiter.api.BeforeAll;
35+
import org.junit.jupiter.api.Test;
36+
import software.amazon.awssdk.http.SdkHttpMethod;
37+
import software.amazon.awssdk.http.SdkHttpRequest;
38+
import software.amazon.awssdk.testutils.FileUtils;
39+
40+
public class NegotiateProxyAuthGeneratorTest {
41+
private static Path tempDir;
42+
private static Path keytabFile;
43+
private static Path ccacheFile;
44+
private static int port;
45+
46+
private static SimpleKdcServer kdc;
47+
48+
private static Configuration config;
49+
50+
@BeforeAll
51+
static void setup() throws IOException, KrbException {
52+
tempDir = Files.createTempDirectory(null);
53+
keytabFile = tempDir.resolve("keytab");
54+
ccacheFile = tempDir.resolve("ccache");
55+
56+
try (Socket freePort = new Socket()) {
57+
freePort.setReuseAddress(true);
58+
freePort.bind(new InetSocketAddress(0));
59+
port = freePort.getLocalPort();
60+
}
61+
62+
kdc = new SimpleKdcServer();
63+
kdc.setKdcRealm("EXAMPLE.COM");
64+
kdc.setKdcHost("localhost");
65+
kdc.setWorkDir(tempDir.toFile());
66+
kdc.setKdcTcpPort(port);
67+
kdc.init();
68+
kdc.start();
69+
70+
kdc.createPrincipal("alice@EXAMPLE.COM", "alicePassword");
71+
kdc.createAndExportPrincipals(keytabFile.toFile(), "HTTP/localhost@EXAMPLE.COM");
72+
73+
// initialize the ticket cache
74+
KrbClient krbClient = kdc.getKrbClient();
75+
TgtTicket tgt = krbClient.requestTgt("alice@EXAMPLE.COM", "alicePassword");
76+
krbClient.storeTicket(tgt, ccacheFile.toFile());
77+
78+
// Override config so we look at the testing cache instead of the real system cache
79+
config = new Configuration() {
80+
@Override
81+
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
82+
Map<String, String> opts = new HashMap<>();
83+
opts.put("useTicketCache", "true");
84+
opts.put("ticketCache", ccacheFile.toAbsolutePath().toString());
85+
opts.put("doNotPrompt", "true");
86+
return new AppConfigurationEntry[] {
87+
new AppConfigurationEntry(
88+
"com.sun.security.auth.module.Krb5LoginModule",
89+
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, opts)
90+
};
91+
}
92+
};
93+
94+
}
95+
96+
@AfterAll
97+
static void teardown() throws KrbException {
98+
kdc.stop();
99+
FileUtils.cleanUpTestDirectory(tempDir);
100+
}
101+
102+
@Test
103+
void generateAuthParams_configValid_successfullyGeneratesToken() {
104+
NegotiateProxyAuthGenerator authGenerator = new NegotiateProxyAuthGenerator(config);
105+
106+
SdkHttpRequest request = SdkHttpRequest.builder()
107+
.protocol("http")
108+
.host("localhost")
109+
.method(SdkHttpMethod.GET)
110+
.build();
111+
112+
assertThat(authGenerator.generateAuthParams(request)).startsWith("YII");
113+
}
114+
115+
}

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@
152152
<bytebuddy.version>1.17.5</bytebuddy.version>
153153
<archunit.version>1.3.0</archunit.version>
154154
<json-schema-validator.version>1.5.4</json-schema-validator.version>
155+
<kerb-simplekdc.version>2.0.3</kerb-simplekdc.version>
155156

156157
<!-- build plugin dependencies-->
157158
<maven.surefire.version>3.1.2</maven.surefire.version>

0 commit comments

Comments
 (0)