🔥 Remove kubeconfig and rely on service account
This commit is contained in:
parent
a42768ca49
commit
33f95bad5a
@ -51,7 +51,7 @@ public class LogResource
|
|||||||
|
|
||||||
public List<KubernetesLog> getLogs(Pod pod, LocalDateTime from)
|
public List<KubernetesLog> getLogs(Pod pod, LocalDateTime from)
|
||||||
{
|
{
|
||||||
String command = String.format("kubectl --kubeconfig=%s logs %s -n %s --timestamps --tail=1000", clientProvider.pathToKubeconfig(), pod.getMetadata().getName(), pod.getMetadata().getNamespace());
|
String command = String.format("kubectl logs %s -n %s --timestamps --tail=1000", pod.getMetadata().getName(), pod.getMetadata().getNamespace());
|
||||||
List<KubernetesLog> result = new ArrayList<>();
|
List<KubernetesLog> result = new ArrayList<>();
|
||||||
List<String> logs = processRunner.runToLines(command);
|
List<String> logs = processRunner.runToLines(command);
|
||||||
for (String log : logs)
|
for (String log : logs)
|
||||||
|
|||||||
@ -44,7 +44,7 @@ public class PodResource
|
|||||||
private List<EnvVar> getVars(Pod pod)
|
private List<EnvVar> getVars(Pod pod)
|
||||||
{
|
{
|
||||||
List<EnvVar> result = new ArrayList<>();
|
List<EnvVar> result = new ArrayList<>();
|
||||||
List<String> lines = processRunner.runToLines(String.format("kubectl --kubeconfig=%s exec -it %s -n %s -- env", clientProvider.pathToKubeconfig(), pod.getMetadata().getName(), pod.getMetadata().getNamespace()));
|
List<String> lines = processRunner.runToLines(String.format("kubectl exec -it %s -n %s -- env", pod.getMetadata().getName(), pod.getMetadata().getNamespace()));
|
||||||
for (String line : lines)
|
for (String line : lines)
|
||||||
{
|
{
|
||||||
int indexOfFirstEquals = line.indexOf("=");
|
int indexOfFirstEquals = line.indexOf("=");
|
||||||
|
|||||||
@ -8,19 +8,29 @@ import dev.dinauer.utils.ClientProvider;
|
|||||||
import io.fabric8.kubernetes.client.Watch;
|
import io.fabric8.kubernetes.client.Watch;
|
||||||
import io.fabric8.kubernetes.client.Watcher;
|
import io.fabric8.kubernetes.client.Watcher;
|
||||||
import io.fabric8.kubernetes.client.WatcherException;
|
import io.fabric8.kubernetes.client.WatcherException;
|
||||||
|
import io.quarkus.security.UnauthorizedException;
|
||||||
|
import io.smallrye.jwt.auth.principal.JWTParser;
|
||||||
|
import io.smallrye.jwt.auth.principal.ParseException;
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.websocket.CloseReason;
|
||||||
import jakarta.websocket.OnClose;
|
import jakarta.websocket.OnClose;
|
||||||
import jakarta.websocket.OnOpen;
|
import jakarta.websocket.OnOpen;
|
||||||
import jakarta.websocket.Session;
|
import jakarta.websocket.Session;
|
||||||
import jakarta.websocket.server.PathParam;
|
import jakarta.websocket.server.PathParam;
|
||||||
import jakarta.websocket.server.ServerEndpoint;
|
import jakarta.websocket.server.ServerEndpoint;
|
||||||
|
import jakarta.ws.rs.QueryParam;
|
||||||
|
import jakarta.ws.rs.WebApplicationException;
|
||||||
import org.eclipse.microprofile.context.ManagedExecutor;
|
import org.eclipse.microprofile.context.ManagedExecutor;
|
||||||
|
import org.eclipse.microprofile.jwt.JsonWebToken;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@ServerEndpoint("/watch/{resource-type}/{namespace}")
|
@ServerEndpoint("/watch/{resource-type}/{namespace}")
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
@ -28,16 +38,25 @@ public class ResourceWebsocket
|
|||||||
{
|
{
|
||||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
Logger LOG;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ClientProvider clientProvider;
|
ClientProvider clientProvider;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ManagedExecutor executor;
|
ManagedExecutor executor;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
JWTParser parser;
|
||||||
|
|
||||||
private final Map<Session, Watch> sessions = new HashMap<>();
|
private final Map<Session, Watch> sessions = new HashMap<>();
|
||||||
|
|
||||||
@OnOpen
|
@OnOpen
|
||||||
public void onOpen(Session session, @PathParam("resource-type") String resourceType, @PathParam("namespace") String namespace)
|
public void onOpen(Session session, @PathParam("resource-type") String resourceType, @PathParam("namespace") String namespace) throws ParseException
|
||||||
|
{
|
||||||
|
JsonWebToken token = getToken(session.getQueryString());
|
||||||
|
if (isValid(token))
|
||||||
{
|
{
|
||||||
executor.runAsync(() ->
|
executor.runAsync(() ->
|
||||||
{
|
{
|
||||||
@ -51,22 +70,27 @@ public class ResourceWebsocket
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sessions.put(session, clientProvider.getClient().pods().inNamespace(namespace).watch(getWatcher(session)));
|
send(session, EventType.INIT, clientProvider.getClient().pods().inNamespace(namespace).list().getItems());
|
||||||
|
sessions.put(session, clientProvider.getClient().pods().inNamespace(namespace).withResourceVersion(version).watch(getWatcher(session)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ResourceType.CONFIG_MAP.equals(resourceType))
|
if (ResourceType.CONFIG_MAP.equals(resourceType))
|
||||||
{
|
{
|
||||||
|
String version = clientProvider.getClient().configMaps().inAnyNamespace().list().getMetadata().getResourceVersion();
|
||||||
if (isGlobal(namespace))
|
if (isGlobal(namespace))
|
||||||
{
|
{
|
||||||
sessions.put(session, clientProvider.getClient().configMaps().inAnyNamespace().watch(getWatcher(session)));
|
send(session, EventType.INIT, clientProvider.getClient().configMaps().inAnyNamespace().list().getItems());
|
||||||
|
sessions.put(session, clientProvider.getClient().configMaps().inAnyNamespace().withResourceVersion(version).watch(getWatcher(session)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sessions.put(session, clientProvider.getClient().configMaps().inNamespace(namespace).watch(getWatcher(session)));
|
send(session, EventType.INIT, clientProvider.getClient().configMaps().inNamespace(namespace).list().getItems());
|
||||||
|
sessions.put(session, clientProvider.getClient().configMaps().inNamespace(namespace).withResourceVersion(version).watch(getWatcher(session)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OnClose
|
@OnClose
|
||||||
public void onClose(Session session)
|
public void onClose(Session session)
|
||||||
@ -76,6 +100,14 @@ public class ResourceWebsocket
|
|||||||
{
|
{
|
||||||
watch.close();
|
watch.close();
|
||||||
}
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> Watcher<T> getWatcher(Session session)
|
private <T> Watcher<T> getWatcher(Session session)
|
||||||
@ -119,4 +151,31 @@ public class ResourceWebsocket
|
|||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private JsonWebToken getToken(String query) throws ParseException
|
||||||
|
{
|
||||||
|
for (String param : query.split("&"))
|
||||||
|
{
|
||||||
|
String[] sections = param.split("=", 2);
|
||||||
|
if (sections.length == 2)
|
||||||
|
{
|
||||||
|
if (sections[0].equals("token"))
|
||||||
|
{
|
||||||
|
return parser.parse(sections[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.error("Token cannot be null.");
|
||||||
|
throw new RuntimeException("Token cannot be null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValid(JsonWebToken token)
|
||||||
|
{
|
||||||
|
Optional<String> purpose = token.claim("purpose");
|
||||||
|
if (purpose.isPresent())
|
||||||
|
{
|
||||||
|
return purpose.get().equals("ws:connect");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,51 @@
|
|||||||
|
package dev.dinauer.inspect.websocket;
|
||||||
|
|
||||||
|
import com.arjuna.ats.internal.arjuna.Header;
|
||||||
|
import io.quarkus.security.Authenticated;
|
||||||
|
import io.quarkus.security.UnauthorizedException;
|
||||||
|
import io.smallrye.jwt.auth.principal.JWTParser;
|
||||||
|
import io.smallrye.jwt.auth.principal.ParseException;
|
||||||
|
import io.smallrye.jwt.build.Jwt;
|
||||||
|
import io.smallrye.jwt.build.JwtClaimsBuilder;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.ws.rs.HeaderParam;
|
||||||
|
import jakarta.ws.rs.POST;
|
||||||
|
import jakarta.ws.rs.Path;
|
||||||
|
import jakarta.ws.rs.Produces;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import jakarta.ws.rs.core.SecurityContext;
|
||||||
|
import org.eclipse.microprofile.jwt.JsonWebToken;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Path("/websocket-session")
|
||||||
|
public class WebsocketSessionResource
|
||||||
|
{
|
||||||
|
@Inject
|
||||||
|
JsonWebToken token;
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Authenticated
|
||||||
|
@Produces(MediaType.TEXT_PLAIN)
|
||||||
|
public String getSession()
|
||||||
|
{
|
||||||
|
if (token != null)
|
||||||
|
{
|
||||||
|
JwtClaimsBuilder builder = Jwt.upn(token.getName()).issuer(token.getIssuer()).expiresAt(ZonedDateTime.now().plusSeconds(15).toInstant());
|
||||||
|
Optional<List<String>> namespaces = token.claim("namespaces");
|
||||||
|
if (namespaces.isPresent())
|
||||||
|
{
|
||||||
|
builder = builder.claim("namespaces", namespaces.get());
|
||||||
|
}
|
||||||
|
Optional<List<String>> resources = token.claim("resources");
|
||||||
|
if (resources.isPresent())
|
||||||
|
{
|
||||||
|
builder = builder.claim("resources", resources.get());
|
||||||
|
}
|
||||||
|
return builder.claim("purpose", "ws:connect").sign();
|
||||||
|
}
|
||||||
|
throw new UnauthorizedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,8 @@
|
|||||||
package dev.dinauer.login;
|
package dev.dinauer.login;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import io.fabric8.kubernetes.api.model.ObjectMeta;
|
||||||
import io.smallrye.jwt.build.Jwt;
|
import io.smallrye.jwt.build.Jwt;
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
@ -10,12 +13,15 @@ import org.jboss.logging.Logger;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Path("/login")
|
@Path("/login")
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class LoginResource
|
public class LoginResource
|
||||||
{
|
{
|
||||||
|
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Logger LOG;
|
Logger LOG;
|
||||||
|
|
||||||
@ -33,7 +39,11 @@ public class LoginResource
|
|||||||
UserEntity user = userOptional.get();
|
UserEntity user = userOptional.get();
|
||||||
if(BcryptUtil.matches(login.password(), user.getPassword()))
|
if(BcryptUtil.matches(login.password(), user.getPassword()))
|
||||||
{
|
{
|
||||||
return Jwt.upn(user.getId()).expiresAt(ZonedDateTime.now().plusDays(15).toInstant()).groups(user.getRoles()).sign();
|
return Jwt
|
||||||
|
.upn(user.getId())
|
||||||
|
.expiresAt(ZonedDateTime.now().plusDays(15).toInstant())
|
||||||
|
.groups(user.getRoles())
|
||||||
|
.sign();
|
||||||
}
|
}
|
||||||
LOG.info("Cannot access user. Forbidden");
|
LOG.info("Cannot access user. Forbidden");
|
||||||
throw new ForbiddenException();
|
throw new ForbiddenException();
|
||||||
|
|||||||
@ -59,7 +59,7 @@ public class TopNodesService
|
|||||||
|
|
||||||
private List<String> runTopNodesCommand()
|
private List<String> runTopNodesCommand()
|
||||||
{
|
{
|
||||||
String command = String.format("kubectl --kubeconfig=%s top nodes --no-headers", clientProvider.pathToKubeconfig());
|
String command = String.format("kubectl top nodes --no-headers");
|
||||||
return processRunner.runToLines(command);
|
return processRunner.runToLines(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,19 +14,11 @@ import java.io.File;
|
|||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class ClientProvider
|
public class ClientProvider
|
||||||
{
|
{
|
||||||
@ConfigProperty(name = "dev.dinauer.kobooboo.kubeconfigs.dir")
|
|
||||||
String configFilePath;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Vertx vertx;
|
Vertx vertx;
|
||||||
|
|
||||||
public KubernetesClient getClient()
|
public KubernetesClient getClient()
|
||||||
{
|
{
|
||||||
return new KubernetesClientBuilder().withConfig(Config.fromKubeconfig(new File(configFilePath))).withHttpClientFactory(new VertxHttpClientFactory(vertx.getDelegate())).build();
|
return new KubernetesClientBuilder().withHttpClientFactory(new VertxHttpClientFactory(vertx.getDelegate())).build();
|
||||||
}
|
|
||||||
|
|
||||||
public String pathToKubeconfig()
|
|
||||||
{
|
|
||||||
return configFilePath;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,13 +3,9 @@ quarkus.http.root-path=/api
|
|||||||
%dev.quarkus.http.cors.origins=/.*/
|
%dev.quarkus.http.cors.origins=/.*/
|
||||||
%dev.quarkus.http.port=9090
|
%dev.quarkus.http.port=9090
|
||||||
|
|
||||||
%dev,test.dev.dinauer.kobooboo.kubeconfigs.dir=/var/lib/kubooboo/config
|
|
||||||
dev.dinauer.kubooboo.work.dir=/var/lib/kubooboo/work
|
dev.dinauer.kubooboo.work.dir=/var/lib/kubooboo/work
|
||||||
%dev.dev.dinauer.kobooboo.kubeconfigs.dir=/home/andreas/.kube/config
|
|
||||||
%dev.dev.dinauer.kubooboo.work.dir=/home/andreas/Documents/dev/kubooboo/backend/src/main/resources/dev
|
%dev.dev.dinauer.kubooboo.work.dir=/home/andreas/Documents/dev/kubooboo/backend/src/main/resources/dev
|
||||||
|
|
||||||
%prod.dev.dinauer.kobooboo.kubeconfigs.dir=${KUBECONFIG_LOCATION}
|
|
||||||
|
|
||||||
# Keys
|
# Keys
|
||||||
%prod.smallrye.jwt.sign.key.location=${PRIVATE_KEY_LOCATION}
|
%prod.smallrye.jwt.sign.key.location=${PRIVATE_KEY_LOCATION}
|
||||||
%prod.mp.jwt.verify.publickey.location=${PUBLIC_KEY_LOCATION}
|
%prod.mp.jwt.verify.publickey.location=${PUBLIC_KEY_LOCATION}
|
||||||
@ -18,7 +14,7 @@ dev.dinauer.kubooboo.work.dir=/var/lib/kubooboo/work
|
|||||||
%dev.mp.jwt.verify.publickey.location=publicKey.pem
|
%dev.mp.jwt.verify.publickey.location=publicKey.pem
|
||||||
|
|
||||||
# Postgres
|
# Postgres
|
||||||
%dev.quarkus.datasource.db-kind = postgresql
|
quarkus.datasource.db-kind = postgresql
|
||||||
%dev.quarkus.datasource.username = postgres
|
%dev.quarkus.datasource.username = postgres
|
||||||
%dev.quarkus.datasource.password = postgres
|
%dev.quarkus.datasource.password = postgres
|
||||||
%dev.quarkus.datasource.jdbc.url = jdbc:postgresql://localhost:6666/postgres
|
%dev.quarkus.datasource.jdbc.url = jdbc:postgresql://localhost:6666/postgres
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user