DIRKRB-745 Add ktadd cmd for remote admin tool. Contributed by Jiayi Liu.
diff --git a/kerby-common/kerby-xdr/src/main/java/org/apache/kerby/xdr/type/XdrBytes.java b/kerby-common/kerby-xdr/src/main/java/org/apache/kerby/xdr/type/XdrBytes.java
index 105ff74..e987adf 100644
--- a/kerby-common/kerby-xdr/src/main/java/org/apache/kerby/xdr/type/XdrBytes.java
+++ b/kerby-common/kerby-xdr/src/main/java/org/apache/kerby/xdr/type/XdrBytes.java
@@ -6,24 +6,38 @@
* 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 org.apache.kerby.xdr.type;
import org.apache.kerby.xdr.XdrDataType;
import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/*
+ * From RFC 4506 :
+ *
+ * 0 1 2 3 4 5 ...
+ * +-----+-----+-----+-----+-----+-----+...+-----+-----+...+-----+
+ * | length n |byte0|byte1|...| n-1 | 0 |...| 0 |
+ * +-----+-----+-----+-----+-----+-----+...+-----+-----+...+-----+
+ * |<-------4 bytes------->|<------n bytes------>|<---r bytes--->|
+ * |<----n+r (where (n+r) mod 4 = 0)---->|
+ * VARIABLE-LENGTH OPAQUE
+ */
public class XdrBytes extends XdrSimple<byte[]> {
+ private int padding;
public XdrBytes() {
this(null);
@@ -35,11 +49,63 @@
@Override
protected void toValue() throws IOException {
-
+ byte[] bytes = getBytes();
+ byte[] header = new byte[4];
+ System.arraycopy(bytes, 0, header, 0, 4);
+ int byteArrayLen = ByteBuffer.wrap(header).getInt();
+ int paddingBytes = (4 - (byteArrayLen % 4)) % 4;
+ validatePaddingBytes(paddingBytes);
+ setPadding(paddingBytes);
+
+ if (bytes.length != byteArrayLen + 4 + paddingBytes) {
+ int totalLength = byteArrayLen + 4 + paddingBytes;
+ byte[] resetBytes = ByteBuffer.allocate(totalLength)
+ .put(getBytes(), 0, totalLength).array();
+ /**reset bytes in case the enum type is in a struct or union*/
+ setBytes(resetBytes);
+ }
+
+ byte[] content = new byte[byteArrayLen];
+ if (bytes.length > 1) {
+ System.arraycopy(bytes, 4, content, 0, byteArrayLen);
+ }
+ setValue(content);
}
@Override
- protected void toBytes() {
+ protected int encodingBodyLength() throws IOException {
+ if (getValue() != null) {
+ padding = (4 - getValue().length % 4) % 4;
+ return getValue().length + padding + 4;
+ }
+ return 0;
+ }
+ @Override
+ protected void toBytes() throws IOException {
+ if (getValue() != null) {
+ byte[] bytes = new byte[encodingBodyLength()];
+ int length = getValue().length;
+ bytes[0] = (byte) (length >> 24);
+ bytes[1] = (byte) (length >> 16);
+ bytes[2] = (byte) (length >> 8);
+ bytes[3] = (byte) (length);
+ System.arraycopy(getValue(), 0, bytes, 4, length);
+ setBytes(bytes);
+ }
+ }
+
+ public void setPadding(int padding) {
+ this.padding = padding;
+ }
+
+ public int getPadding() {
+ return padding;
+ }
+
+ private void validatePaddingBytes(int paddingBytes) throws IOException {
+ if (paddingBytes < 0 || paddingBytes > 3) {
+ throw new IOException("Bad padding number: " + paddingBytes + ", should be in [0, 3]");
+ }
}
}
diff --git a/kerby-common/kerby-xdr/src/main/java/org/apache/kerby/xdr/type/XdrString.java b/kerby-common/kerby-xdr/src/main/java/org/apache/kerby/xdr/type/XdrString.java
index 539f17c..1da6d3a 100644
--- a/kerby-common/kerby-xdr/src/main/java/org/apache/kerby/xdr/type/XdrString.java
+++ b/kerby-common/kerby-xdr/src/main/java/org/apache/kerby/xdr/type/XdrString.java
@@ -32,7 +32,7 @@
/*
* From RFC 4506 :
*
- * 0 1 2 3 4 5 ...
+ * 0 1 2 3 4 5 ...
* +-----+-----+-----+-----+-----+-----+...+-----+-----+...+-----+
* | length n |byte0|byte1|...| n-1 | 0 |...| 0 |
* +-----+-----+-----+-----+-----+-----+...+-----+-----+...+-----+
diff --git a/kerby-kerb/kerb-admin-server/pom.xml b/kerby-kerb/kerb-admin-server/pom.xml
index 0a3ac9c..183afcc 100644
--- a/kerby-kerb/kerb-admin-server/pom.xml
+++ b/kerby-kerb/kerb-admin-server/pom.xml
@@ -52,5 +52,22 @@
<artifactId>kerb-common</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.kerby</groupId>
+ <artifactId>kerb-simplekdc</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.kerby</groupId>
+ <artifactId>json-backend</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git a/kerby-kerb/kerb-admin-server/src/main/java/org/apache/kerby/kerberos/kerb/admin/server/kadmin/AdminServerHandler.java b/kerby-kerb/kerb-admin-server/src/main/java/org/apache/kerby/kerberos/kerb/admin/server/kadmin/AdminServerHandler.java
index 7875fa6..9db44e4 100644
--- a/kerby-kerb/kerb-admin-server/src/main/java/org/apache/kerby/kerberos/kerb/admin/server/kadmin/AdminServerHandler.java
+++ b/kerby-kerb/kerb-admin-server/src/main/java/org/apache/kerby/kerberos/kerb/admin/server/kadmin/AdminServerHandler.java
@@ -30,15 +30,20 @@
import org.apache.kerby.kerberos.kerb.admin.message.GetprincsRep;
import org.apache.kerby.kerberos.kerb.admin.message.KadminCode;
import org.apache.kerby.kerberos.kerb.admin.message.RenamePrincipalRep;
+import org.apache.kerby.kerberos.kerb.admin.message.KeytabMessageCode;
+import org.apache.kerby.kerberos.kerb.admin.message.ExportKeytabRep;
import org.apache.kerby.xdr.XdrDataType;
import org.apache.kerby.xdr.XdrFieldInfo;
import org.apache.kerby.xdr.type.XdrStructType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.util.Arrays;
import java.util.List;
/**
@@ -91,9 +96,13 @@
responseMessage = handleRenamePrincipalReq(localKadmin, fieldInfos);
break;
case GET_PRINCS_REQ:
- System.out.println("message type getPrincs req");
+ System.out.println("message type: get principals req");
responseMessage = handleGetprincsReq(localKadmin, fieldInfos);
break;
+ case EXPORT_KEYTAB_REQ:
+ System.out.println("message type: export keytab req");
+ responseMessage = handleExportKeytabReq(localKadmin, fieldInfos);
+ break;
default:
throw new KrbException("AdminMessageType error, can not handle the type: " + type);
}
@@ -199,6 +208,40 @@
return responseError;
}
}
+
+ private ByteBuffer handleExportKeytabReq(LocalKadmin localKadmin, XdrFieldInfo[] fieldInfos) throws IOException {
+ String principals = ((String) fieldInfos[2].getValue());
+
+ if (principals != null) {
+ List<String> princList = stringToList(principals);
+ if (princList.size() != 0) {
+ LOG.info("Exporting keytab file for " + principals + "...");
+ File tempDir = Files.createTempDirectory("ktadd").toFile();
+ File keytabFile = new File(tempDir, princList.get(0)
+ .replace('/', '-')
+ .replace('*', '-')
+ .replace('?', '-')
+ + ".keytab");
+ try {
+ localKadmin.exportKeytab(keytabFile, princList);
+ LOG.info("Create keytab file for principals successfully.");
+ ByteBuffer responseMessage = infoPackageTool(keytabFile, "exportKeytab");
+ return responseMessage;
+ } catch (KrbException e) {
+ String error = "Failed to export keytab. " + e.toString();
+ ByteBuffer responseError = infoPackageTool(error, "exportKeytab");
+ return responseError;
+ }
+ } else {
+ String error = "No matched principals.";
+ ByteBuffer responseError = infoPackageTool(error, "exportKeytab");
+ return responseError;
+ }
+ }
+ String error = "Failed to export keytab.";
+ ByteBuffer responseError = infoPackageTool(error, "exportKeytab");
+ return responseError;
+ }
private ByteBuffer infoPackageTool(String message, String dealType) throws IOException {
AdminMessage adminMessage = null;
@@ -226,6 +269,23 @@
return KadminCode.encodeMessage(adminMessage);
}
+
+ private ByteBuffer infoPackageTool(File keytabFile, String dealType) throws IOException {
+ AdminMessage adminMessage = null;
+ XdrFieldInfo[] xdrFieldInfos = new XdrFieldInfo[3];
+ if ("exportKeytab".equals(dealType)) {
+ adminMessage = new ExportKeytabRep();
+ xdrFieldInfos[0] = new XdrFieldInfo(0, XdrDataType.ENUM, AdminMessageType.EXPORT_KEYTAB_REP);
+ }
+
+ xdrFieldInfos[1] = new XdrFieldInfo(1, XdrDataType.INTEGER, 1);
+ xdrFieldInfos[2] = new XdrFieldInfo(2, XdrDataType.BYTES, Files.readAllBytes(keytabFile.toPath()));
+
+ KeytabMessageCode value = new KeytabMessageCode(xdrFieldInfos);
+ adminMessage.setMessageBuffer(ByteBuffer.wrap(value.encode()));
+
+ return KadminCode.encodeMessage(adminMessage);
+ }
private String listToString(List<String> list) {
if (list.isEmpty()) {
@@ -238,4 +298,22 @@
}
return result.toString();
}
+
+ private List<String> stringToList(String str) {
+ if (str == null || str.isEmpty()) {
+ return null;
+ }
+ String[] principals = str.split(" ");
+ for (int i = 0; i < principals.length; i++) {
+ principals[i] = fixPrincipal(principals[i]);
+ }
+ return Arrays.asList(principals);
+ }
+
+ private String fixPrincipal(String principal) {
+ if (!principal.contains("@")) {
+ principal += "@" + adminServerContext.getAdminRealm();
+ }
+ return principal;
+ }
}
diff --git a/kerby-kerb/kerb-admin-server/src/test/java/org/apache/kerby/kerberos/kerb/admin/server/RemoteKadminTest.java b/kerby-kerb/kerb-admin-server/src/test/java/org/apache/kerby/kerberos/kerb/admin/server/RemoteKadminTest.java
new file mode 100644
index 0000000..a539e1e
--- /dev/null
+++ b/kerby-kerb/kerb-admin-server/src/test/java/org/apache/kerby/kerberos/kerb/admin/server/RemoteKadminTest.java
@@ -0,0 +1,255 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.kerby.kerberos.kerb.admin.server;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.kerby.kerberos.kerb.admin.AuthUtil;
+import org.apache.kerby.kerberos.kerb.admin.kadmin.local.LocalKadminImpl;
+import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.AdminClient;
+import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.AdminConfig;
+import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.AdminUtil;
+import org.apache.kerby.kerberos.kerb.admin.server.kadmin.AdminServer;
+import org.apache.kerby.kerberos.kerb.admin.server.kadmin.AdminServerConfig;
+import org.apache.kerby.kerberos.kerb.identity.backend.BackendConfig;
+import org.apache.kerby.kerberos.kerb.keytab.Keytab;
+import org.apache.kerby.kerberos.kerb.server.KdcConfig;
+import org.apache.kerby.kerberos.kerb.server.KdcServer;
+import org.apache.kerby.kerberos.kerb.server.SimpleKdcServer;
+import org.apache.kerby.kerberos.kerb.transport.KrbNetwork;
+import org.apache.kerby.kerberos.kerb.transport.KrbTransport;
+import org.apache.kerby.kerberos.kerb.transport.TransportPair;
+import org.apache.kerby.kerberos.kerb.type.base.PrincipalName;
+import org.apache.kerby.util.NetworkUtil;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.security.auth.Subject;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslClient;
+import java.io.File;
+import java.nio.ByteBuffer;
+import java.security.PrivilegedAction;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+public class RemoteKadminTest {
+ private static final Logger LOG = LoggerFactory.getLogger(RemoteKadminTest.class);
+
+ protected static File testDir = new File(System.getProperty("test.dir", "target"));
+ private static File testClassDir = new File(testDir, "test-classes");
+ private static File confDir = new File(testClassDir, "conf");
+ private static File workDir = new File(testDir, "work-dir");
+
+ private static final String SERVER_HOST = "localhost";
+
+ private static final String ADMIN_PRINCIPAL = "kadmin/EXAMPLE.COM@EXAMPLE.COM";
+ private static final String PROTOCOL_PRINCIPAL = "adminprotocol/localhost@EXAMPLE.COM";
+
+ private static KdcServer kdcServer;
+ private static LocalKadminImpl localKadmin;
+ private static AdminServer adminServer;
+
+ private static SimpleKdcServer buildMiniKdc() throws Exception {
+ KdcConfig config = new KdcConfig();
+ BackendConfig backendConfig = new BackendConfig();
+ backendConfig.addIniConfig(new File(confDir, "backend.conf"));
+ SimpleKdcServer kdc = new SimpleKdcServer(config, backendConfig);
+ kdc.setWorkDir(workDir);
+
+ kdc.setKdcHost(SERVER_HOST);
+ int kdcPort = NetworkUtil.getServerPort();
+ kdc.setAllowTcp(true);
+ kdc.setAllowUdp(false);
+ kdc.setKdcTcpPort(kdcPort);
+ LOG.info("Starting KDC server at " + SERVER_HOST + ":" + kdcPort);
+ kdc.init();
+ return kdc;
+ }
+
+ private static void buildLocalKadmin() throws Exception {
+ localKadmin = new LocalKadminImpl(kdcServer.getKdcSetting(), kdcServer.getIdentityService());
+ localKadmin.addPrincipal(PROTOCOL_PRINCIPAL);
+ localKadmin.exportKeytab(new File(workDir, "protocol.keytab"), PROTOCOL_PRINCIPAL);
+ localKadmin.exportKeytab(new File(workDir, "admin.keytab"), ADMIN_PRINCIPAL);
+ }
+
+ private static AdminServer buildAdminServer() throws Exception {
+ AdminServer server = new AdminServer(confDir);
+ AdminServerConfig config = server.getAdminServerConfig();
+ server.setAdminHost(config.getAdminHost());
+ server.setAllowTcp(true);
+ server.setAllowUdp(false);
+ server.setAdminServerPort(config.getAdminPort());
+
+ server.init();
+ return server;
+ }
+
+ private AdminClient buildKadminRemoteClient() throws Exception {
+ final AdminConfig config = new AdminConfig();
+ config.addKrb5Config(new File(confDir, "adminClient.conf"));
+ AdminClient adminClient = new AdminClient(config);
+ adminClient.setAdminRealm(config.getAdminRealm());
+ adminClient.setAllowTcp(true);
+ adminClient.setAllowUdp(false);
+ adminClient.setAdminTcpPort(config.getAdminPort());
+
+ adminClient.init();
+ doSaslHandShake(adminClient, config);
+ return adminClient;
+ }
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ workDir.mkdirs();
+
+ kdcServer = buildMiniKdc();
+ kdcServer.start();
+
+ buildLocalKadmin();
+
+ adminServer = buildAdminServer();
+ adminServer.start();
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ adminServer.stop();
+ kdcServer.stop();
+ FileUtils.deleteDirectory(workDir);
+ }
+
+ @Test
+ public void remoteListPrincipalTest() throws Exception {
+ AdminClient adminClient = buildKadminRemoteClient();
+ List<String> principals = adminClient.requestGetprincs();
+ assertTrue(principals.contains("kadmin/EXAMPLE.COM@EXAMPLE.COM"));
+ assertTrue(principals.contains("krbtgt/EXAMPLE.COM@EXAMPLE.COM"));
+ assertTrue(principals.contains("adminprotocol/localhost@EXAMPLE.COM"));
+
+ principals = adminClient.requestGetprincsWithExp("k*");
+ assertTrue(principals.contains("kadmin/EXAMPLE.COM@EXAMPLE.COM"));
+ assertTrue(principals.contains("krbtgt/EXAMPLE.COM@EXAMPLE.COM"));
+ assertFalse(principals.contains("adminprotocol/localhost@EXAMPLE.COM"));
+ }
+
+ @Test
+ public void remoteModifyPrincipalTest() throws Exception {
+ AdminClient adminClient = buildKadminRemoteClient();
+ String testPrincipal = "test/EXAMPLE.COM";
+ String renamePrincipal = "test_rename/EXAMPLE.COM";
+ adminClient.requestAddPrincipal(testPrincipal);
+ assertTrue("Remote kadmin add principal test failed.",
+ localKadmin.getPrincipals().contains(testPrincipal + "@EXAMPLE.COM"));
+
+ adminClient.requestRenamePrincipal(testPrincipal, renamePrincipal);
+ assertTrue("Remote kadmin rename principal test failed, the renamed principal does not exist.",
+ localKadmin.getPrincipals().contains(renamePrincipal + "@EXAMPLE.COM"));
+ assertFalse("Remote kadmin rename principal test failed, the old principal still exists.",
+ localKadmin.getPrincipals().contains(testPrincipal + "@EXAMPLE.COM"));
+
+ adminClient.requestDeletePrincipal(renamePrincipal);
+ assertFalse("Remote kadmin delete principal test failed.",
+ localKadmin.getPrincipals().contains(renamePrincipal + "@EXAMPLE.COM"));
+ }
+
+ @Test
+ public void remoteKtaddTest() throws Exception {
+ AdminClient adminClient = buildKadminRemoteClient();
+ String testPrincipal = "test/EXAMPLE.COM";
+ File keytabOutput = new File(testDir, "test.keytab");
+ localKadmin.addPrincipal(testPrincipal);
+ adminClient.requestExportKeytab(keytabOutput, testPrincipal);
+ Keytab localKeytab = Keytab.loadKeytab(keytabOutput);
+ List<String> principalNames = localKeytab.getPrincipals()
+ .stream().map(PrincipalName::getName).collect(Collectors.toList());
+ assertTrue(principalNames.contains(testPrincipal + "@EXAMPLE.COM"));
+ }
+
+ private void doSaslHandShake(AdminClient adminClient, AdminConfig config) throws Exception {
+ TransportPair tpair = AdminUtil.getTransportPair(adminClient.getSetting());
+ KrbNetwork network = new KrbNetwork();
+ network.setSocketTimeout(adminClient.getSetting().getTimeout());
+ KrbTransport transport = network.connect(tpair);
+ Subject subject = AuthUtil.loginUsingKeytab(ADMIN_PRINCIPAL, new File(config.getKeyTabFile()));
+ Subject.doAs(subject, (PrivilegedAction<Object>) () -> {
+ try {
+ Map<String, String> props = new HashMap<>();
+ props.put(Sasl.QOP, "auth-conf");
+ props.put(Sasl.SERVER_AUTH, "true");
+ SaslClient saslClient = null;
+
+ String protocol = config.getProtocol();
+ String serverName = config.getServerName();
+ saslClient = Sasl.createSaslClient(new String[]{"GSSAPI"}, null,
+ protocol, serverName, props, null);
+ if (saslClient == null) {
+ System.out.println("Unable to find client implementation for: GSSAPI");
+ return null;
+ }
+ byte[] response;
+ response = saslClient.hasInitialResponse()
+ ? saslClient.evaluateChallenge(new byte[0]) : new byte[0];
+
+ sendMessage(response, saslClient, transport);
+
+ ByteBuffer message = transport.receiveMessage();
+
+ while (!saslClient.isComplete()) {
+ int ssComplete = message.getInt();
+ if (ssComplete == 0) {
+ System.out.println("Sasl Server completed");
+ }
+ byte[] arr = new byte[message.remaining()];
+ message.get(arr);
+ byte[] challenge = saslClient.evaluateChallenge(arr);
+
+ sendMessage(challenge, saslClient, transport);
+
+ if (!saslClient.isComplete()) {
+ message = transport.receiveMessage();
+ }
+ }
+ } catch (Exception e) {
+ LOG.warn(e.getMessage());
+ }
+ return null;
+ });
+ }
+
+ private void sendMessage(byte[] challenge, SaslClient saslClient, KrbTransport transport) throws Exception {
+ ByteBuffer buffer = ByteBuffer.allocate(challenge.length + 8);
+ buffer.putInt(challenge.length + 4);
+ int scComplete = saslClient.isComplete() ? 0 : 1;
+
+ buffer.putInt(scComplete);
+ buffer.put(challenge);
+ buffer.flip();
+ transport.sendMessage(buffer);
+ }
+}
diff --git a/kerby-kerb/kerb-admin-server/src/test/resources/conf/adminClient.conf b/kerby-kerb/kerb-admin-server/src/test/resources/conf/adminClient.conf
new file mode 100644
index 0000000..e970ae6
--- /dev/null
+++ b/kerby-kerb/kerb-admin-server/src/test/resources/conf/adminClient.conf
@@ -0,0 +1,7 @@
+[libdefaults]
+ default_realm = EXAMPLE.COM
+ admin_host = localhost
+ admin_port = 65417
+ keytab_file = target/work-dir/admin.keytab
+ protocol = adminprotocol
+ server_name = localhost
\ No newline at end of file
diff --git a/kerby-kerb/kerb-admin-server/src/test/resources/conf/adminServer.conf b/kerby-kerb/kerb-admin-server/src/test/resources/conf/adminServer.conf
new file mode 100644
index 0000000..06a96c7
--- /dev/null
+++ b/kerby-kerb/kerb-admin-server/src/test/resources/conf/adminServer.conf
@@ -0,0 +1,8 @@
+[libdefaults]
+ default_realm = EXAMPLE.COM
+ admin_realm = EXAMPLE.COM
+ admin_host = localhost
+ admin_port = 65417
+ keytab_file = target/work-dir/protocol.keytab
+ protocol = adminprotocol
+ server_name = localhost
\ No newline at end of file
diff --git a/kerby-kerb/kerb-admin-server/src/test/resources/conf/backend.conf b/kerby-kerb/kerb-admin-server/src/test/resources/conf/backend.conf
new file mode 100644
index 0000000..797bf0c
--- /dev/null
+++ b/kerby-kerb/kerb-admin-server/src/test/resources/conf/backend.conf
@@ -0,0 +1,2 @@
+kdc_identity_backend = org.apache.kerby.kerberos.kdc.identitybackend.JsonIdentityBackend
+backend.json.dir = target/work-dir/jsonbackend
\ No newline at end of file
diff --git a/kerby-kerb/kerb-admin-server/src/test/resources/conf/kdc.conf b/kerby-kerb/kerb-admin-server/src/test/resources/conf/kdc.conf
new file mode 100644
index 0000000..a3948fa
--- /dev/null
+++ b/kerby-kerb/kerb-admin-server/src/test/resources/conf/kdc.conf
@@ -0,0 +1,23 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF 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.
+#
+
+[kdcdefaults]
+ kdc_host = localhost
+ kdc_udp_port = 8866
+ kdc_tcp_port = 8866
+ kdc_realm = EXAMPLE.COM
diff --git a/kerby-kerb/kerb-admin-server/src/test/resources/conf/krb5.conf b/kerby-kerb/kerb-admin-server/src/test/resources/conf/krb5.conf
new file mode 100644
index 0000000..def3c96
--- /dev/null
+++ b/kerby-kerb/kerb-admin-server/src/test/resources/conf/krb5.conf
@@ -0,0 +1,29 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF 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.
+#
+
+[libdefaults]
+ kdc_realm = EXAMPLE.COM
+ default_realm = EXAMPLE.COM
+ udp_preference_limit = 4096
+ kdc_tcp_port = 8866
+ kdc_udp_port = 8866
+
+[realms]
+ EXAMPLE.COM = {
+ kdc = localhost:8866
+ }
diff --git a/kerby-kerb/kerb-admin/pom.xml b/kerby-kerb/kerb-admin/pom.xml
index 6f2f599..84b110f 100644
--- a/kerby-kerb/kerb-admin/pom.xml
+++ b/kerby-kerb/kerb-admin/pom.xml
@@ -42,5 +42,15 @@
<artifactId>kerby-xdr</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.jline</groupId>
+ <artifactId>jline</artifactId>
+ <version>${jline.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.jcraft</groupId>
+ <artifactId>jsch</artifactId>
+ <version>${jsch.version}</version>
+ </dependency>
</dependencies>
</project>
diff --git a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/RemoteAdminClientTool.java b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/RemoteAdminClientTool.java
index 96d68ce..e8d8531 100644
--- a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/RemoteAdminClientTool.java
+++ b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/RemoteAdminClientTool.java
@@ -23,12 +23,12 @@
import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.AdminClient;
import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.AdminConfig;
import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.AdminUtil;
-import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.command.RemoteAddPrincipalCommand;
import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.command.RemoteCommand;
+import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.command.RemoteAddPrincipalCommand;
import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.command.RemoteDeletePrincipalCommand;
-import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.command.RemoteGetprincsCommand;
-import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.command.RemotePrintUsageCommand;
import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.command.RemoteRenamePrincipalCommand;
+import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.command.RemoteGetprincsCommand;
+import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.command.RemoteKeytabAddCommand;
import org.apache.kerby.kerberos.kerb.common.KrbUtil;
import org.apache.kerby.kerberos.kerb.server.KdcConfig;
import org.apache.kerby.kerberos.kerb.server.KdcUtil;
@@ -36,6 +36,14 @@
import org.apache.kerby.kerberos.kerb.transport.KrbTransport;
import org.apache.kerby.kerberos.kerb.transport.TransportPair;
import org.apache.kerby.util.OSUtil;
+import org.jline.reader.LineReader;
+import org.jline.reader.LineReaderBuilder;
+import org.jline.reader.Completer;
+import org.jline.reader.UserInterruptException;
+import org.jline.reader.EndOfFileException;
+import org.jline.reader.impl.completer.StringsCompleter;
+import org.jline.terminal.Terminal;
+import org.jline.terminal.TerminalBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -50,7 +58,6 @@
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Map;
-import java.util.Scanner;
/**
* Command use of remote admin
@@ -59,7 +66,7 @@
private static final Logger LOG = LoggerFactory.getLogger(RemoteAdminClientTool.class);
private static final byte[] EMPTY = new byte[0];
private static KrbTransport transport;
- private static final String PROMPT = RemoteAdminClientTool.class.getSimpleName() + ".local:";
+ private static final String PROMPT = RemoteAdminClientTool.class.getSimpleName() + ".remote";
private static final String USAGE = (OSUtil.isWindows()
? "Usage: bin\\remote-admin-client.cmd" : "Usage: sh bin/remote-admin-client.sh")
+ " <conf-file>\n"
@@ -77,8 +84,10 @@
+ " Delete principal\n"
+ "rename_principal, renprinc\n"
+ " Rename principal\n"
- + "listprincs\n"
- + " List principals\n";
+ + "list_principals, listprincs\n"
+ + " List principals\n"
+ + "ktadd, xst\n"
+ + " Add entry(s) to a keytab\n";
public static void main(String[] args) throws Exception {
AdminClient adminClient;
@@ -205,13 +214,28 @@
System.out.println("enter \"command\" to see legal commands.");
- try (Scanner scanner = new Scanner(System.in, "UTF-8")) {
- String input = scanner.nextLine();
+ Completer completer = new StringsCompleter("add_principal", "delete_principal", "rename_principal",
+ "list_principals", "ktadd");
- while (!(input.equals("quit") || input.equals("exit") || input.equals("q"))) {
- excute(adminClient, input);
- System.out.print(PROMPT);
- input = scanner.nextLine();
+ Terminal terminal = null;
+ try {
+ terminal = TerminalBuilder.terminal();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ LineReader lineReader = LineReaderBuilder.builder().completer(completer).terminal(terminal).build();
+
+ while (true) {
+ try {
+ String line = lineReader.readLine(PROMPT + ": ");
+ if ("quit".equals(line) || "exit".equals(line) || "q".equals(line)) {
+ break;
+ }
+ execute(adminClient, line);
+ } catch (UserInterruptException | EndOfFileException ex) {
+ break;
+ } catch (KrbException e) {
+ System.err.println(e.getMessage());
}
}
}
@@ -235,7 +259,7 @@
}
}
- private static void excute(AdminClient adminClient, String input) throws KrbException {
+ private static void execute(AdminClient adminClient, String input) throws KrbException {
input = input.trim();
if (input.startsWith("command")) {
System.out.println(LEGAL_COMMANDS);
@@ -253,10 +277,12 @@
} else if (input.startsWith("rename_principal")
|| input.startsWith("renprinc")) {
executor = new RemoteRenamePrincipalCommand(adminClient);
- } else if (input.startsWith("list_principals")) {
+ } else if (input.startsWith("list_principals")
+ || input.startsWith("listprincs")) {
executor = new RemoteGetprincsCommand(adminClient);
- } else if (input.startsWith("listprincs")) {
- executor = new RemotePrintUsageCommand();
+ } else if (input.startsWith("ktadd")
+ || input.startsWith("xst")) {
+ executor = new RemoteKeytabAddCommand(adminClient);
} else {
System.out.println(LEGAL_COMMANDS);
return;
diff --git a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/local/AdminHelper.java b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/local/AdminHelper.java
index c69148d..b3fc01f 100644
--- a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/local/AdminHelper.java
+++ b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/local/AdminHelper.java
@@ -32,6 +32,7 @@
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;
@@ -101,6 +102,24 @@
}
/**
+ * Load keytab from Input stream.
+ *
+ * @param keytabInputStream The Input stream
+ * @return The keytab load from keytab file
+ * @throws KrbException If there is a problem loading the Input stream
+ */
+ public static Keytab loadKeytab(InputStream keytabInputStream) throws KrbException {
+ Keytab keytab;
+ try {
+ keytab = Keytab.loadKeytab(keytabInputStream);
+ } catch (IOException e) {
+ throw new KrbException("Failed to load keytab", e);
+ }
+
+ return keytab;
+ }
+
+ /**
* If keytab file does not exist, create a new keytab,
* otherwise load keytab from keytab file.
*
diff --git a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/AdminClient.java b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/AdminClient.java
index 01c336d..ee15058 100644
--- a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/AdminClient.java
+++ b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/AdminClient.java
@@ -201,4 +201,14 @@
List<String> principalLists = remote.getPrincipals(exp);
return principalLists;
}
+
+ public void requestExportKeytab(File keytabFile, String principal) throws KrbException {
+ Kadmin remote = new RemoteKadminImpl(innerClient);
+ remote.exportKeytab(keytabFile, principal);
+ }
+
+ public void requestExportKeytab(File keytabFile, List<String> principals) throws KrbException {
+ Kadmin remote = new RemoteKadminImpl(innerClient);
+ remote.exportKeytab(keytabFile, principals);
+ }
}
diff --git a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/AdminHandler.java b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/AdminHandler.java
index 9debfdd..ddcb362 100644
--- a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/AdminHandler.java
+++ b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/AdminHandler.java
@@ -21,10 +21,11 @@
import org.apache.kerby.kerberos.kerb.KrbException;
import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.request.AdminRequest;
-import org.apache.kerby.kerberos.kerb.admin.message.AdminMessageCode;
-import org.apache.kerby.kerberos.kerb.admin.message.AdminMessageType;
import org.apache.kerby.kerberos.kerb.admin.message.AdminReq;
import org.apache.kerby.kerberos.kerb.admin.message.KadminCode;
+import org.apache.kerby.kerberos.kerb.admin.message.AdminMessageCode;
+import org.apache.kerby.kerberos.kerb.admin.message.AdminMessageType;
+import org.apache.kerby.kerberos.kerb.admin.message.KeytabMessageCode;
import org.apache.kerby.xdr.XdrFieldInfo;
import org.apache.kerby.xdr.type.XdrStructType;
@@ -147,6 +148,35 @@
return princalsList;
}
+
+ public byte[] onResponseMessageForBytesArray(AdminRequest adminRequest,
+ ByteBuffer responseMessage) throws KrbException {
+ byte[] keytabFileBytes = null;
+
+ XdrStructType decoded = new KeytabMessageCode();
+ try {
+ decoded.decode(responseMessage);
+ } catch (IOException e) {
+ throw new KrbException("On response message failed.", e);
+ }
+ XdrFieldInfo[] fieldInfos = decoded.getValue().getXdrFieldInfos();
+ AdminMessageType type = (AdminMessageType) fieldInfos[0].getValue();
+
+ switch (type) {
+ case EXPORT_KEYTAB_REP:
+ if (adminRequest.getAdminReq().getAdminMessageType() == AdminMessageType.EXPORT_KEYTAB_REQ) {
+ keytabFileBytes = (byte[]) fieldInfos[2].getValue();
+ } else {
+ throw new KrbException("Response message type error: need "
+ + AdminMessageType.EXPORT_KEYTAB_REP);
+ }
+ break;
+ default:
+ throw new KrbException("Response message type error: " + type);
+ }
+
+ return keytabFileBytes;
+ }
/**
* Send message to kdc.
@@ -159,4 +189,6 @@
ByteBuffer requestMessage) throws IOException;
protected abstract List<String> handleRequestForList(AdminRequest adminRequest) throws KrbException;
+
+ protected abstract byte[] handleRequestForBytes(AdminRequest adminRequest) throws KrbException;
}
diff --git a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/RemoteKadminImpl.java b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/RemoteKadminImpl.java
index ccc3419..69846b3 100644
--- a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/RemoteKadminImpl.java
+++ b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/RemoteKadminImpl.java
@@ -22,22 +22,28 @@
import org.apache.kerby.KOptions;
import org.apache.kerby.kerberos.kerb.KrbException;
import org.apache.kerby.kerberos.kerb.admin.kadmin.Kadmin;
+import org.apache.kerby.kerberos.kerb.admin.kadmin.local.AdminHelper;
import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.impl.DefaultAdminHandler;
import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.impl.InternalAdminClient;
import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.request.AddPrincipalRequest;
import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.request.AdminRequest;
+import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.request.ExportKeytabRequest;
import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.request.DeletePrincipalRequest;
-import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.request.GetprincsRequest;
import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.request.RenamePrincipalRequest;
+import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.request.GetprincsRequest;
import org.apache.kerby.kerberos.kerb.common.KrbUtil;
+import org.apache.kerby.kerberos.kerb.keytab.Keytab;
import org.apache.kerby.kerberos.kerb.transport.KrbNetwork;
import org.apache.kerby.kerberos.kerb.transport.KrbTransport;
import org.apache.kerby.kerberos.kerb.transport.TransportPair;
+import org.apache.kerby.kerberos.kerb.type.base.PrincipalName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
+import java.util.Collections;
import java.util.List;
/**
@@ -120,13 +126,26 @@
@Override
public void exportKeytab(File keytabFile,
String principal) throws KrbException {
-
+ exportKeytab(keytabFile, Collections.singletonList(principal));
}
@Override
public void exportKeytab(File keytabFile,
List<String> principals) throws KrbException {
-
+ String principalsStr = listToString(principals);
+ AdminRequest exportKeytabRequest = new ExportKeytabRequest(principalsStr);
+ exportKeytabRequest.setTransport(transport);
+ AdminHandler adminHandler = new DefaultAdminHandler();
+ byte[] keytabFileBytes = adminHandler.handleRequestForBytes(exportKeytabRequest);
+
+ Keytab keytab = AdminHelper.loadKeytab(new ByteArrayInputStream(keytabFileBytes));
+ Keytab outputKeytab = AdminHelper.createOrLoadKeytab(keytabFile);
+
+ // If original keytab file exists, we need to merge keytab entries
+ for (PrincipalName principal: keytab.getPrincipals()) {
+ outputKeytab.addKeytabEntries(keytab.getKeytabEntries(principal));
+ }
+ AdminHelper.storeKeytab(outputKeytab, keytabFile);
}
@Override
@@ -206,4 +225,16 @@
public void release() throws KrbException {
}
+
+ private String listToString(List<String> list) {
+ if (list.isEmpty()) {
+ return null;
+ }
+ //Both speed and safety,so use StringBuilder
+ StringBuilder result = new StringBuilder();
+ for (int i = 0; i < list.size(); i++) {
+ result.append(list.get(i)).append(" ");
+ }
+ return result.toString();
+ }
}
diff --git a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/command/RemoteKeytabAddCommand.java b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/command/RemoteKeytabAddCommand.java
new file mode 100644
index 0000000..0ff489e
--- /dev/null
+++ b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/command/RemoteKeytabAddCommand.java
@@ -0,0 +1,94 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.kerby.kerberos.kerb.admin.kadmin.remote.command;
+
+import org.apache.kerby.kerberos.kerb.KrbException;
+import org.apache.kerby.kerberos.kerb.admin.kadmin.remote.AdminClient;
+
+import java.io.File;
+import java.util.List;
+
+public class RemoteKeytabAddCommand extends RemoteCommand {
+ private static final String USAGE = "Usage: ktadd [-k[eytab] keytab] "
+ + "[principal | -glob princ-exp] [...]\n"
+ + "\tExample:\n"
+ + "\t\tktadd hello@TEST.COM -k /keytab/location\n";
+ private static final String DEFAULT_KEYTAB_FILE_LOCATION = "/etc/krb5.keytab";
+
+ public RemoteKeytabAddCommand(AdminClient adminClient) {
+ super(adminClient);
+ }
+
+ @Override
+ public void execute(String input) throws KrbException {
+ String[] items = input.split("\\s+");
+
+ if (items.length < 2) {
+ System.err.println(USAGE);
+ return;
+ }
+
+ String principal = null;
+ String keytabFileLocation = null;
+ boolean glob = false;
+
+ int index = 1;
+ while (index < items.length) {
+ String command = items[index];
+ if (command.equals("-k")) {
+ index++;
+ if (index >= items.length) {
+ System.err.println(USAGE);
+ return;
+ }
+ keytabFileLocation = items[index].trim();
+ } else if (command.equals("-glob")) {
+ glob = true;
+ } else if (!command.startsWith("-")) {
+ principal = command;
+ }
+ index++;
+ }
+
+ if (keytabFileLocation == null) {
+ keytabFileLocation = DEFAULT_KEYTAB_FILE_LOCATION;
+ }
+ File keytabFile = new File(keytabFileLocation);
+
+ if (principal == null) {
+ System.out.println((glob ? "princ-exp" : "principal") + " not specified!");
+ System.err.println(USAGE);
+ return;
+ }
+
+ try {
+ if (glob) {
+ List<String> principals = adminClient.requestGetprincsWithExp(principal);
+ adminClient.requestExportKeytab(keytabFile, principals);
+ } else {
+ adminClient.requestExportKeytab(keytabFile, principal);
+ }
+ System.out.println("Export Keytab to " + keytabFileLocation);
+ } catch (KrbException e) {
+ System.err.println("Principal \"" + principal + "\" fail to add entry to keytab. "
+ + e.toString());
+ }
+ }
+}
diff --git a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/impl/DefaultAdminHandler.java b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/impl/DefaultAdminHandler.java
index 3d05b50..41615a7 100644
--- a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/impl/DefaultAdminHandler.java
+++ b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/impl/DefaultAdminHandler.java
@@ -76,4 +76,20 @@
return prinicalList;
}
+
+ @Override
+ protected byte[] handleRequestForBytes(AdminRequest adminRequest) throws KrbException {
+ super.handleRequest(adminRequest);
+
+ KrbTransport transport = adminRequest.getTransport();
+ ByteBuffer receiveMessage = null;
+ byte[] keytabFileBytes;
+ try {
+ receiveMessage = transport.receiveMessage();
+ keytabFileBytes = super.onResponseMessageForBytesArray(adminRequest, receiveMessage);
+ } catch (IOException e) {
+ throw new KrbException("Admin receives response message failed", e);
+ }
+ return keytabFileBytes;
+ }
}
diff --git a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/request/ExportKeytabRequest.java b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/request/ExportKeytabRequest.java
new file mode 100644
index 0000000..eb75670
--- /dev/null
+++ b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/kadmin/remote/request/ExportKeytabRequest.java
@@ -0,0 +1,61 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.kerby.kerberos.kerb.admin.kadmin.remote.request;
+
+import org.apache.kerby.kerberos.kerb.KrbException;
+import org.apache.kerby.kerberos.kerb.admin.message.AdminMessageCode;
+import org.apache.kerby.kerberos.kerb.admin.message.AdminMessageType;
+import org.apache.kerby.kerberos.kerb.admin.message.ExportKeytabReq;
+import org.apache.kerby.xdr.XdrDataType;
+import org.apache.kerby.xdr.XdrFieldInfo;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+public class ExportKeytabRequest extends AdminRequest {
+
+ public ExportKeytabRequest(String principal) {
+ super(principal);
+ }
+
+ @Override
+ public void process() throws KrbException {
+ super.process();
+
+ ExportKeytabReq exportKeytabReq = new ExportKeytabReq();
+
+ XdrFieldInfo[] xdrFieldInfos = new XdrFieldInfo[3];
+ xdrFieldInfos[0] = new XdrFieldInfo(0, XdrDataType.ENUM, AdminMessageType.EXPORT_KEYTAB_REQ);
+ xdrFieldInfos[1] = new XdrFieldInfo(1, XdrDataType.INTEGER, 1);
+ xdrFieldInfos[2] = new XdrFieldInfo(2, XdrDataType.STRING, getPrincipal());
+
+ AdminMessageCode value = new AdminMessageCode(xdrFieldInfos);
+ byte[] encodeBytes;
+ try {
+ encodeBytes = value.encode();
+ } catch (IOException e) {
+ throw new KrbException("Xdr encode error when generate get principals request.", e);
+ }
+ ByteBuffer messageBuffer = ByteBuffer.wrap(encodeBytes);
+ exportKeytabReq.setMessageBuffer(messageBuffer);
+
+ setAdminReq(exportKeytabReq);
+ }
+}
diff --git a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/AdminMessageType.java b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/AdminMessageType.java
index f44187e..0688179 100644
--- a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/AdminMessageType.java
+++ b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/AdminMessageType.java
@@ -42,7 +42,9 @@
RENAME_PRINCIPAL_REQ(4),
RENAME_PRINCIPAL_REP(5),
GET_PRINCS_REQ(6),
- GET_PRINCS_REP(7);
+ GET_PRINCS_REP(7),
+ EXPORT_KEYTAB_REQ(8),
+ EXPORT_KEYTAB_REP(9);
private int value;
diff --git a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/ExportKeytabRep.java b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/ExportKeytabRep.java
new file mode 100644
index 0000000..f9356b2
--- /dev/null
+++ b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/ExportKeytabRep.java
@@ -0,0 +1,26 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.kerby.kerberos.kerb.admin.message;
+
+public class ExportKeytabRep extends AdminRep {
+ public ExportKeytabRep() {
+ super(AdminMessageType.EXPORT_KEYTAB_REP);
+ }
+}
diff --git a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/ExportKeytabReq.java b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/ExportKeytabReq.java
new file mode 100644
index 0000000..cd687a0
--- /dev/null
+++ b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/ExportKeytabReq.java
@@ -0,0 +1,26 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.kerby.kerberos.kerb.admin.message;
+
+public class ExportKeytabReq extends AdminReq {
+ public ExportKeytabReq() {
+ super(AdminMessageType.EXPORT_KEYTAB_REQ);
+ }
+}
diff --git a/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/KeytabMessageCode.java b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/KeytabMessageCode.java
new file mode 100644
index 0000000..234e68d
--- /dev/null
+++ b/kerby-kerb/kerb-admin/src/main/java/org/apache/kerby/kerberos/kerb/admin/message/KeytabMessageCode.java
@@ -0,0 +1,82 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.kerby.kerberos.kerb.admin.message;
+
+import org.apache.kerby.xdr.XdrDataType;
+import org.apache.kerby.xdr.XdrFieldInfo;
+import org.apache.kerby.xdr.type.XdrStructType;
+import org.apache.kerby.xdr.type.XdrType;
+import org.apache.kerby.xdr.type.XdrInteger;
+import org.apache.kerby.xdr.type.XdrString;
+import org.apache.kerby.xdr.type.XdrBytes;
+import org.apache.kerby.xdr.type.AbstractXdrType;
+
+/**
+ * An extend XdrStructType to encode and decode ExportKeytab message.
+ */
+public class KeytabMessageCode extends XdrStructType {
+ public KeytabMessageCode() {
+ super(XdrDataType.STRUCT);
+ }
+
+ public KeytabMessageCode(XdrFieldInfo[] fieldInfos) {
+ super(XdrDataType.STRUCT, fieldInfos);
+ }
+
+ @Override
+ protected void getStructTypeInstance(XdrType[] fields, XdrFieldInfo[] fieldInfos) {
+ for (int i = 0; i < fieldInfos.length; i++) {
+ switch (fieldInfos[i].getDataType()) {
+ case INTEGER:
+ fields[i] = new XdrInteger((Integer) fieldInfos[i].getValue());
+ break;
+ case ENUM:
+ fields[i] = new AdminMessageEnum((AdminMessageType) fieldInfos[i].getValue());
+ break;
+ case STRING:
+ fields[i] = new XdrString((String) fieldInfos[i].getValue());
+ break;
+ case BYTES:
+ fields[i] = new XdrBytes((byte[]) fieldInfos[i].getValue());
+ break;
+ default:
+ fields[i] = null;
+ }
+ }
+ }
+
+ @Override
+ protected XdrStructType fieldsToValues(AbstractXdrType[] fields) {
+ XdrFieldInfo[] xdrFieldInfos = new XdrFieldInfo[3];
+ xdrFieldInfos[0] = new XdrFieldInfo(0, XdrDataType.ENUM, fields[0].getValue());
+ xdrFieldInfos[1] = new XdrFieldInfo(1, XdrDataType.INTEGER, fields[1].getValue());
+ xdrFieldInfos[2] = new XdrFieldInfo(2, XdrDataType.BYTES, fields[2].getValue());
+ return new KeytabMessageCode(xdrFieldInfos);
+ }
+
+ @Override
+ protected AbstractXdrType[] getAllFields() {
+ AbstractXdrType[] fields = new AbstractXdrType[4];
+ fields[0] = new AdminMessageEnum();
+ fields[1] = new XdrInteger();
+ fields[2] = new XdrBytes();
+ return fields;
+ }
+}