DIRKRB-689 Create principals and export keytabs with host and role.
diff --git a/has-project/has-common/src/main/java/org/apache/kerby/has/common/Hadmin.java b/has-project/has-common/src/main/java/org/apache/kerby/has/common/Hadmin.java
new file mode 100644
index 0000000..882b10f
--- /dev/null
+++ b/has-project/has-common/src/main/java/org/apache/kerby/has/common/Hadmin.java
@@ -0,0 +1,35 @@
+/**
+ * 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.has.common;
+
+import java.io.File;
+
+/**
+ * Server side admin facilities from remote, similar to MIT kadmin remote mode.
+ */
+public interface Hadmin {
+
+
+ String addPrincByRole(String host, String role) throws HasException;
+
+ File getKeytabByHostAndRole(String host, String role) throws HasException;
+
+ void getHostRoles();
+}
diff --git a/has-project/has-server/src/main/java/org/apache/kerby/has/server/admin/LocalHadmin.java b/has-project/has-server/src/main/java/org/apache/kerby/has/server/admin/LocalHadmin.java
new file mode 100644
index 0000000..4661d87
--- /dev/null
+++ b/has-project/has-server/src/main/java/org/apache/kerby/has/server/admin/LocalHadmin.java
@@ -0,0 +1,140 @@
+/**
+ * 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.has.server.admin;
+
+import org.apache.kerby.has.common.Hadmin;
+import org.apache.kerby.has.common.HasException;
+import org.apache.kerby.has.server.HasServer;
+import org.apache.kerby.has.server.web.HostRoleType;
+import org.apache.kerby.kerberos.kerb.KrbException;
+import org.apache.kerby.kerberos.kerb.admin.kadmin.local.LocalKadmin;
+import org.apache.kerby.kerberos.kerb.admin.kadmin.local.LocalKadminImpl;
+import org.apache.kerby.kerberos.kerb.identity.backend.BackendConfig;
+import org.apache.kerby.kerberos.kerb.server.KdcConfig;
+import org.apache.kerby.kerberos.kerb.server.KdcSetting;
+import org.apache.kerby.kerberos.kerb.server.KdcUtil;
+import org.apache.kerby.kerberos.kerb.server.ServerSetting;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+
+public class LocalHasAdmin implements Hadmin {
+ public static final Logger LOG = LoggerFactory.getLogger(LocalHasAdmin.class);
+
+ private final ServerSetting serverSetting;
+ private LocalKadmin kadmin;
+
+ public LocalHasAdmin(HasServer hasServer) throws KrbException {
+ if (hasServer.getKdcServer() == null) {
+ throw new RuntimeException("Could not get HAS KDC server, please start KDC first.");
+ }
+ this.serverSetting = hasServer.getKdcServer().getKdcSetting();
+
+ kadmin = new LocalKadminImpl(serverSetting);
+ }
+
+ /**
+ * Construct with prepared conf dir.
+ *
+ * @param confDir The path of conf dir
+ * @throws KrbException e
+ */
+ public LocalHasAdmin(File confDir) throws KrbException {
+ KdcConfig tmpKdcConfig = KdcUtil.getKdcConfig(confDir);
+ if (tmpKdcConfig == null) {
+ tmpKdcConfig = new KdcConfig();
+ }
+
+ BackendConfig tmpBackendConfig = KdcUtil.getBackendConfig(confDir);
+ if (tmpBackendConfig == null) {
+ tmpBackendConfig = new BackendConfig();
+ }
+
+ this.serverSetting = new KdcSetting(tmpKdcConfig, tmpBackendConfig);
+ kadmin = new LocalKadminImpl(serverSetting);
+ }
+
+ @Override
+ public String addPrincByRole(String host, String role) throws HasException {
+ String result = "";
+ String realm = "/" + host + "@" + kadmin.getKdcConfig().getKdcRealm();
+ String[] princs = HostRoleType.valueOf(role).getPrincs();
+ if (princs == null) {
+ LOG.error("Cannot find the role of : " + role);
+ return "Cannot find the role of : " + role;
+ }
+ for (String princ : princs) {
+ try {
+ kadmin.addPrincipal(princ + realm);
+ LOG.info("Success to add princ :" + princ + realm);
+ result = result + "Success to add princ :" + princ + realm + "\n";
+ } catch (KrbException e) {
+ LOG.info(e.getMessage());
+ result = e.getMessage() + "\n";
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public File getKeytabByHostAndRole(String host, String role) throws HasException {
+ String realm = "/" + host + "@" + kadmin.getKdcConfig().getKdcRealm();
+ File path = new File("/tmp/" + System.currentTimeMillis());
+ path.mkdirs();
+ File keytab = new File(path, role + "-" + host + ".keytab");
+ if (keytab.exists()) {
+ keytab.delete();
+ }
+ String[] princs = HostRoleType.valueOf(role).getPrincs();
+ for (String princ : princs) {
+ try {
+ if (kadmin.getPrincipal(princ + realm) == null) {
+ continue;
+ }
+ } catch (KrbException e) {
+ throw new HasException(e);
+ }
+ try {
+ kadmin.exportKeytab(keytab, princ + realm);
+ } catch (KrbException e) {
+ throw new HasException(e);
+ }
+ }
+ return keytab;
+ }
+
+ @Override
+ public void getHostRoles() {
+ for (HostRoleType role : HostRoleType.values()) {
+ System.out.print("\tHostRole: " + role.getName()
+ + ", PrincipalNames: ");
+ String[] princs = role.getPrincs();
+ for (int j = 0; j < princs.length; j++) {
+ System.out.print(princs[j]);
+ if (j == princs.length - 1) {
+ System.out.println();
+ } else {
+ System.out.print(", ");
+ }
+ }
+ }
+ }
+}
diff --git a/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/HostRoleType.java b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/HostRoleType.java
new file mode 100644
index 0000000..104a41f
--- /dev/null
+++ b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/HostRoleType.java
@@ -0,0 +1,55 @@
+/**
+ * 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.has.server.web;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+@InterfaceAudience.Public
+@InterfaceStability.Stable
+public enum HostRoleType {
+ HDFS("HDFS", new String[]{"HTTP", "hdfs"}),
+ YARN("YARN", new String[]{"yarn"}),
+ MAPRED("MAPRED", new String[]{"mapred"}),
+ HBASE("HBASE", new String[]{"hbase"}),
+ ZOOKEEPER("ZOOKEEPER", new String[]{"zookeeper"}),
+ SPARK("SPARK", new String[]{"spark"}),
+ HIVE("HIVE", new String[]{"hive"}),
+ OOZIE("OOZIE", new String[]{"oozie"}),
+ HUE("HUE", new String[]{"hue"});
+
+ private String name;
+ private String[] princs;
+
+ HostRoleType(String name, String[] princs) {
+ this.name = name;
+ this.princs = princs;
+ }
+
+ public String[] getPrincs() {
+ return princs;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/HadminApi.java b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/HadminApi.java
new file mode 100644
index 0000000..c769645
--- /dev/null
+++ b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/HadminApi.java
@@ -0,0 +1,215 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.has.server.web.rest;
+
+import org.apache.kerby.has.common.HasException;
+import org.apache.kerby.has.server.HasServer;
+import org.apache.kerby.has.server.admin.LocalHasAdmin;
+import org.apache.kerby.has.server.web.HostRoleType;
+import org.apache.kerby.has.server.web.WebServer;
+import org.apache.kerby.has.server.web.rest.param.HostParam;
+import org.apache.kerby.has.server.web.rest.param.HostRoleParam;
+import org.apache.kerby.kerberos.kerb.KrbException;
+import org.codehaus.jettison.json.JSONArray;
+import org.codehaus.jettison.json.JSONObject;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * HAS Admin web methods implementation.
+ */
+@Path("/admin")
+public class HadminApi {
+
+ @Context
+ private ServletContext context;
+
+ @Context
+ private HttpServletRequest httpRequest;
+
+ private void compressFile(File file, ZipOutputStream out, String basedir) {
+ if (!file.exists()) {
+ return;
+ }
+ try {
+ BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
+ ZipEntry entry = new ZipEntry(basedir + file.getName());
+ out.putNextEntry(entry);
+ int count;
+ byte[] data = new byte[8192];
+ while ((count = bis.read(data, 0, 8192)) != -1) {
+ out.write(data, 0, count);
+ }
+ bis.close();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @PUT
+ @Path("/addprincipalsbyrole")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response addprincipalsbyrole(@Context HttpServletRequest request) {
+ if (httpRequest.isSecure()) {
+ LocalHasAdmin hasAdmin = null;
+ try {
+ hasAdmin = new LocalHasAdmin(WebServer.getHasServerFromContext(context));
+ } catch (KrbException e) {
+ WebServer.LOG.info("Failed to create local hadmin." + e.getMessage());
+ }
+ JSONObject result = new JSONObject();
+ String msg = "";
+ try {
+ StringBuilder data = new StringBuilder();
+ BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()));
+ String s;
+ while ((s = br.readLine()) != null) {
+ data.append(s);
+ }
+ WebServer.LOG.info("Request to create principals by JSON : \n" + data.toString());
+ JSONArray hostArray = new JSONObject(data.toString()).optJSONArray("HOSTS");
+ for (int i = 0; i < hostArray.length(); i++) {
+ JSONObject host = (JSONObject) hostArray.get(i);
+ String[] roles = host.getString("hostRoles").split(",");
+ for (String role : roles) {
+ msg += hasAdmin.addPrincByRole(host.getString("name"), role.toUpperCase());
+ }
+ }
+ result.put("result", "success");
+ result.put("msg", msg);
+ return Response.ok(result.toString()).build();
+ } catch (Exception e) {
+ WebServer.LOG.error("Failed to create principals,because : " + e.getMessage());
+ msg = "Failed to create principals,because : " + e.getMessage();
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
+ }
+ }
+ return Response.status(Response.Status.FORBIDDEN).entity("HTTPS required.\n").build();
+ }
+
+ /**
+ * @param host Hadoop node
+ * @param role Hadoop role
+ * @return Response
+ */
+ @GET
+ @Path("/exportKeytabsbyrole")
+ @Produces(MediaType.TEXT_PLAIN)
+ public Response exportKeytabsbyrole(@QueryParam(HostParam.NAME) @DefaultValue(HostParam.DEFAULT)
+ final HostParam host,
+ @QueryParam(HostRoleParam.NAME) @DefaultValue(HostRoleParam.DEFAULT)
+ final HostRoleParam role) {
+ if (httpRequest.isSecure()) {
+ WebServer.LOG.info("Request to export keytabs.");
+ String msg;
+ LocalHasAdmin hasAdmin;
+ HasServer hasServer;
+ try {
+ hasServer = WebServer.getHasServerFromContext(context);
+ hasAdmin = new LocalHasAdmin(hasServer);
+ } catch (KrbException e) {
+ msg = "Failed to create local hadmin." + e.getMessage();
+ WebServer.LOG.error(msg);
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
+ }
+ if (host.getValue() != null) {
+ if (role.getValue() != null) {
+ try {
+ File file = hasAdmin.getKeytabByHostAndRole(host.getValue(), role.getValue());
+ WebServer.LOG.info("Create keytab file for the " + role.getValue()
+ + " for " + host.getValue());
+ return Response.ok(file).header("Content-Disposition",
+ "attachment; filename=" + role.getValue() + "-"
+ + host.getValue() + ".keytab").build();
+ } catch (HasException e) {
+ msg = "Failed to export keytab File because : " + e.getMessage();
+ WebServer.LOG.error(msg);
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
+ }
+ } else {
+ //export keytabs zip file
+ List<File> keytabs = new ArrayList<>();
+ for (HostRoleType r : HostRoleType.values()) {
+ try {
+ keytabs.add(hasAdmin.getKeytabByHostAndRole(host.getValue(), r.getName()));
+ WebServer.LOG.info("Create keytab file for the " + r.getName()
+ + " for " + host.getValue());
+ } catch (HasException e) {
+ msg = "Failed to export keytab File because : " + e.getMessage();
+ WebServer.LOG.error(msg);
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
+ }
+ }
+ if (keytabs.size() < 1) {
+ msg = "Failed to get the keytab from backend.";
+ WebServer.LOG.error(msg);
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
+ }
+ File path = new File(hasServer.getWorkDir(), "tmp/zip/"
+ + System.currentTimeMillis());
+ path.mkdirs();
+ File keytabZip = new File(path, "keytab.zip");
+ if (keytabZip.exists()) {
+ keytabZip.delete();
+ }
+ try {
+ ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(keytabZip));
+ for (File keytab : keytabs) {
+ compressFile(keytab, zos, "");
+ }
+ zos.close();
+ WebServer.LOG.info("Success to create the keytab.zip.");
+ return Response.ok(keytabZip).header("Content-Disposition",
+ "attachment; filename=keytab.zip").build();
+ } catch (Exception e) {
+ msg = "Failed to create the keytab.zip,because : " + e.getMessage();
+ WebServer.LOG.error(msg);
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
+ }
+ }
+ } else {
+ msg = "The host value is empty.";
+ WebServer.LOG.error(msg);
+ return Response.status(Response.Status.BAD_REQUEST).entity(msg).build();
+ }
+ }
+ return Response.status(Response.Status.FORBIDDEN).entity("HTTPS required.\n").build();
+ }
+}
diff --git a/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/HostParam.java b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/HostParam.java
new file mode 100644
index 0000000..acf0306
--- /dev/null
+++ b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/HostParam.java
@@ -0,0 +1,45 @@
+/**
+ * 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.has.server.web.rest.param;
+
+public class HostParam extends StringParam {
+ /**
+ * Parameter name.
+ */
+ public static final String NAME = "host";
+ /**
+ * Default parameter value.
+ */
+ public static final String DEFAULT = "";
+
+ private static final Domain DOMAIN = new Domain(NAME, null);
+
+ /**
+ * Constructor.
+ *
+ * @param str a string representation of the parameter value.
+ */
+ public HostParam(final String str) {
+ super(DOMAIN, str == null || str.equals(DEFAULT) ? null : str);
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+}
diff --git a/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/HostRoleParam.java b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/HostRoleParam.java
new file mode 100644
index 0000000..72706ff
--- /dev/null
+++ b/has-project/has-server/src/main/java/org/apache/kerby/has/server/web/rest/param/HostRoleParam.java
@@ -0,0 +1,45 @@
+/**
+ * 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.has.server.web.rest.param;
+
+public class HostRoleParam extends StringParam {
+ /**
+ * Parameter name.
+ */
+ public static final String NAME = "role";
+ /**
+ * Default parameter value.
+ */
+ public static final String DEFAULT = "";
+
+ private static final Domain DOMAIN = new Domain(NAME, null);
+
+ /**
+ * Constructor.
+ *
+ * @param str a string representation of the parameter value.
+ */
+ public HostRoleParam(final String str) {
+ super(DOMAIN, str == null || str.equals(DEFAULT) ? null : str);
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+}