You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@streampipes.apache.org by ri...@apache.org on 2022/04/24 12:58:53 UTC
[incubator-streampipes] branch dev updated: [STREAMPIPES-535] Support asymmetric JWT signatures
This is an automated email from the ASF dual-hosted git repository.
riemer pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git
The following commit(s) were added to refs/heads/dev by this push:
new 66dab7c2f [STREAMPIPES-535] Support asymmetric JWT signatures
66dab7c2f is described below
commit 66dab7c2f8f9fdb0d72dfb405e2c71ecfb07aac7
Author: Dominik Riemer <do...@gmail.com>
AuthorDate: Sun Apr 24 14:58:43 2022 +0200
[STREAMPIPES-535] Support asymmetric JWT signatures
---
.../backend/StreamPipesBackendApplication.java | 2 +
.../streampipes/backend/StreamPipesEnvChecker.java | 83 +++++++++++++++++++
streampipes-backend/src/main/resources/banner.txt | 2 +-
.../credentials/StreamPipesTokenCredentials.java | 4 +-
.../apache/streampipes/commons/constants/Envs.java | 3 +
.../streampipes/config/backend/BackendConfig.java | 4 +
.../config/backend/model/JwtSigningMode.java | 6 ++
.../config/backend/model/LocalAuthConfig.java | 38 +++++++++
.../impl/admin/GeneralConfigurationResource.java | 36 +++++++++
.../security/jwt/JwtTokenGenerator.java | 94 ++++++++++++++++++++++
.../streampipes/security/jwt/JwtTokenUtils.java | 77 ++----------------
.../security/jwt/JwtTokenValidator.java | 40 +++++++++
.../streampipes/security/jwt/KeyGenerator.java | 77 ++++++++++++++++++
.../apache/streampipes/security/jwt/KeyUtils.java | 42 ++++++++++
.../user/management/jwt/JwtTokenProvider.java | 32 +++++++-
.../user/management/jwt/SpKeyResolver.java | 18 +++--
ui/src/app/configuration/configuration.module.ts | 88 ++++++++++----------
.../authentication-configuration.component.html | 26 ++++++
.../authentication-configuration.component.scss | 17 ++++
.../authentication-configuration.component.ts | 52 ++++++++++++
.../security-configuration.component.html | 7 ++
.../configuration/shared/configuration.service.ts | 9 ++-
ui/src/app/configuration/shared/multipart-utils.ts | 39 +++++++++
23 files changed, 668 insertions(+), 128 deletions(-)
diff --git a/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesBackendApplication.java b/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesBackendApplication.java
index b71101140..6b0fe4f45 100644
--- a/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesBackendApplication.java
+++ b/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesBackendApplication.java
@@ -100,6 +100,8 @@ public class StreamPipesBackendApplication extends StreamPipesServiceBase {
doInitialSetup();
}
+ new StreamPipesEnvChecker().updateEnvironmentVariables();
+
executorService.schedule(this::startAllPreviouslyStoppedPipelines, 5, TimeUnit.SECONDS);
LOG.info("Pipeline health check will run every {} seconds", HEALTH_CHECK_INTERVAL);
healthCheckExecutorService.scheduleAtFixedRate(new PipelineHealthCheck(),
diff --git a/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesEnvChecker.java b/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesEnvChecker.java
new file mode 100644
index 000000000..db62d1a05
--- /dev/null
+++ b/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesEnvChecker.java
@@ -0,0 +1,83 @@
+/*
+ * 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.streampipes.backend;
+
+import org.apache.streampipes.commons.constants.Envs;
+import org.apache.streampipes.config.backend.BackendConfig;
+import org.apache.streampipes.config.backend.model.JwtSigningMode;
+import org.apache.streampipes.config.backend.model.LocalAuthConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+public class StreamPipesEnvChecker {
+
+ private static final Logger LOG = LoggerFactory.getLogger(StreamPipesEnvChecker.class);
+
+ BackendConfig coreConfig;
+
+ public void updateEnvironmentVariables() {
+ this.coreConfig = BackendConfig.INSTANCE;
+
+ LOG.info("Checking and updating environment variables...");
+ updateJwtSettings();
+ }
+
+ private void updateJwtSettings() {
+ LocalAuthConfig localAuthConfig = coreConfig.getLocalAuthConfig();
+ boolean incompleteConfig = false;
+ if (Envs.SP_JWT_SIGNING_MODE.exists()) {
+ localAuthConfig.setJwtSigningMode(JwtSigningMode.valueOf(Envs.SP_JWT_SIGNING_MODE.getValue()));
+ }
+ if (Envs.SP_JWT_SECRET.exists()) {
+ localAuthConfig.setTokenSecret(Envs.SP_JWT_SECRET.getValue());
+ }
+ if (Envs.SP_JWT_PUBLIC_KEY_LOC.exists()) {
+ try {
+ localAuthConfig.setPublicKey(readPublicKey(Envs.SP_JWT_PUBLIC_KEY_LOC.getValue()));
+ } catch (IOException e) {
+ incompleteConfig = true;
+ LOG.warn("Could not read public key at location " + Envs.SP_JWT_PUBLIC_KEY_LOC);
+ }
+ }
+
+ if (!Envs.SP_JWT_SIGNING_MODE.exists()) {
+ LOG.info("No JWT signing mode provided (using default settings), consult the docs to learn how to provide JWT settings");
+ } else if (localAuthConfig.getJwtSigningMode() == JwtSigningMode.HMAC && !Envs.SP_JWT_SECRET.exists()) {
+ LOG.warn("JWT signing mode set to HMAC but no secret provided (falling back to auto-generated secret), provide a {} variable",
+ Envs.SP_JWT_SECRET.getEnvVariableName());
+ } else if (localAuthConfig.getJwtSigningMode() == JwtSigningMode.RSA &&
+ ((!Envs.SP_JWT_PUBLIC_KEY_LOC.exists() || !Envs.SP_JWT_PRIVATE_KEY_LOC.exists()) || incompleteConfig)) {
+ LOG.warn("JWT signing mode set to RSA but no public or private key location provided, do you provide {} and {} variables?",
+ Envs.SP_JWT_PRIVATE_KEY_LOC.getEnvVariableName(),
+ Envs.SP_JWT_PUBLIC_KEY_LOC.getEnvVariableName());
+ }
+ if (!incompleteConfig) {
+ LOG.info("Updating local auth config with signing mode {}", localAuthConfig.getJwtSigningMode().name());
+ coreConfig.updateLocalAuthConfig(localAuthConfig);
+ }
+ }
+
+ private String readPublicKey(String publicKeyLocation) throws IOException {
+ return Files.readString(Paths.get(publicKeyLocation));
+ }
+}
diff --git a/streampipes-backend/src/main/resources/banner.txt b/streampipes-backend/src/main/resources/banner.txt
index 9ed3ea518..63d07b79d 100644
--- a/streampipes-backend/src/main/resources/banner.txt
+++ b/streampipes-backend/src/main/resources/banner.txt
@@ -3,4 +3,4 @@
|__ | _| _| -__| _ | || __/ || _ | -__|__ --|
|_______|____|__| |_____|___._|__|__|__||___| |__|| __|_____|_____|
|__|
-** StreamPipes Pipeline Management **
+** StreamPipes Core **
diff --git a/streampipes-client/src/main/java/org/apache/streampipes/client/credentials/StreamPipesTokenCredentials.java b/streampipes-client/src/main/java/org/apache/streampipes/client/credentials/StreamPipesTokenCredentials.java
index b6aae0201..dd65a53df 100644
--- a/streampipes-client/src/main/java/org/apache/streampipes/client/credentials/StreamPipesTokenCredentials.java
+++ b/streampipes-client/src/main/java/org/apache/streampipes/client/credentials/StreamPipesTokenCredentials.java
@@ -19,7 +19,7 @@ package org.apache.streampipes.client.credentials;
import org.apache.http.Header;
import org.apache.streampipes.client.http.header.Headers;
-import org.apache.streampipes.security.jwt.JwtTokenUtils;
+import org.apache.streampipes.security.jwt.JwtTokenGenerator;
import java.util.Collections;
import java.util.Date;
@@ -46,7 +46,7 @@ public class StreamPipesTokenCredentials implements CredentialsProvider {
}
private String makeJwtToken() {
- return JwtTokenUtils.makeJwtToken(username, tokenSecret, makeExpirationDate());
+ return JwtTokenGenerator.makeJwtToken(username, tokenSecret, makeExpirationDate());
}
private Date makeExpirationDate() {
diff --git a/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/Envs.java b/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/Envs.java
index 92aeed138..954d3d953 100644
--- a/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/Envs.java
+++ b/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/Envs.java
@@ -24,6 +24,9 @@ public enum Envs {
SP_CONSUL_LOCATION("CONSUL_LOCATION"),
SP_KAFKA_RETENTION_MS("SP_KAFKA_RETENTION_MS"),
SP_JWT_SECRET("JWT_SECRET"),
+ SP_JWT_SIGNING_MODE("SP_JWT_SIGNING_MODE"),
+ SP_JWT_PRIVATE_KEY_LOC("SP_JWT_PRIVATE_KEY_LOC"),
+ SP_JWT_PUBLIC_KEY_LOC("SP_JWT_PUBLIC_KEY_LOC"),
SP_INITIAL_ADMIN_EMAIL("SP_INITIAL_ADMIN_EMAIL"),
SP_INITIAL_ADMIN_PASSWORD("SP_INITIAL_ADMIN_PASSWORD"),
SP_INITIAL_SERVICE_USER("SP_INITIAL_SERVICE_USER"),
diff --git a/streampipes-config/src/main/java/org/apache/streampipes/config/backend/BackendConfig.java b/streampipes-config/src/main/java/org/apache/streampipes/config/backend/BackendConfig.java
index eef333e60..403ac4d66 100644
--- a/streampipes-config/src/main/java/org/apache/streampipes/config/backend/BackendConfig.java
+++ b/streampipes-config/src/main/java/org/apache/streampipes/config/backend/BackendConfig.java
@@ -253,6 +253,10 @@ public enum BackendConfig {
config.setObject(BackendConfigKeys.GENERAL_CONFIG, generalConfig);
}
+ public void updateLocalAuthConfig(LocalAuthConfig authConfig) {
+ config.setObject(BackendConfigKeys.LOCAL_AUTH_CONFIG, authConfig);
+ }
+
public boolean isSetupRunning() {
return config.getBoolean(BackendConfigKeys.IS_SETUP_RUNNING);
}
diff --git a/streampipes-config/src/main/java/org/apache/streampipes/config/backend/model/JwtSigningMode.java b/streampipes-config/src/main/java/org/apache/streampipes/config/backend/model/JwtSigningMode.java
new file mode 100644
index 000000000..11a6df06c
--- /dev/null
+++ b/streampipes-config/src/main/java/org/apache/streampipes/config/backend/model/JwtSigningMode.java
@@ -0,0 +1,6 @@
+package org.apache.streampipes.config.backend.model;
+
+public enum JwtSigningMode {
+ HMAC,
+ RSA
+}
diff --git a/streampipes-config/src/main/java/org/apache/streampipes/config/backend/model/LocalAuthConfig.java b/streampipes-config/src/main/java/org/apache/streampipes/config/backend/model/LocalAuthConfig.java
index 03252331e..044eb0efc 100644
--- a/streampipes-config/src/main/java/org/apache/streampipes/config/backend/model/LocalAuthConfig.java
+++ b/streampipes-config/src/main/java/org/apache/streampipes/config/backend/model/LocalAuthConfig.java
@@ -23,11 +23,20 @@ public class LocalAuthConfig {
private String tokenSecret;
private long tokenExpirationTimeMillis;
+ private JwtSigningMode jwtSigningMode = JwtSigningMode.HMAC;
+
+ private String publicKey;
public static LocalAuthConfig fromDefaults(String jwtSecret) {
return new LocalAuthConfig(jwtSecret, TokenExpirationTimeMillisDefault);
}
+ public static LocalAuthConfig fromDefaults(JwtSigningMode signingMode,
+ String key,
+ long tokenExpirationTimeMillis) {
+ return new LocalAuthConfig(signingMode, key, tokenExpirationTimeMillis);
+ }
+
public static LocalAuthConfig from(String tokenSecret,
long tokenExpirationTimeMillis) {
return new LocalAuthConfig(tokenSecret, tokenExpirationTimeMillis);
@@ -37,9 +46,22 @@ public class LocalAuthConfig {
}
+ private LocalAuthConfig(JwtSigningMode jwtSigningMode,
+ String key,
+ long tokenExpirationTimeMillis) {
+ this.jwtSigningMode = jwtSigningMode;
+ this.tokenExpirationTimeMillis = tokenExpirationTimeMillis;
+ if (jwtSigningMode == JwtSigningMode.HMAC) {
+ this.tokenSecret = key;
+ } else {
+ this.publicKey = key;
+ }
+ }
+
private LocalAuthConfig(String tokenSecret,
long tokenExpirationTimeMillis) {
this.tokenSecret = tokenSecret;
+ this.jwtSigningMode = JwtSigningMode.HMAC;
this.tokenExpirationTimeMillis = tokenExpirationTimeMillis;
}
@@ -58,4 +80,20 @@ public class LocalAuthConfig {
public void setTokenExpirationTimeMillis(long tokenExpirationTimeMillis) {
this.tokenExpirationTimeMillis = tokenExpirationTimeMillis;
}
+
+ public JwtSigningMode getJwtSigningMode() {
+ return jwtSigningMode;
+ }
+
+ public void setJwtSigningMode(JwtSigningMode jwtSigningMode) {
+ this.jwtSigningMode = jwtSigningMode;
+ }
+
+ public String getPublicKey() {
+ return publicKey;
+ }
+
+ public void setPublicKey(String publicKey) {
+ this.publicKey = publicKey;
+ }
}
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/GeneralConfigurationResource.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/GeneralConfigurationResource.java
index 8761d6181..ad41cc80d 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/GeneralConfigurationResource.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/admin/GeneralConfigurationResource.java
@@ -22,12 +22,19 @@ import org.apache.streampipes.config.backend.model.GeneralConfig;
import org.apache.streampipes.rest.core.base.impl.AbstractAuthGuardedRestResource;
import org.apache.streampipes.rest.security.AuthConstants;
import org.apache.streampipes.rest.shared.annotation.JacksonSerialized;
+import org.glassfish.jersey.media.multipart.BodyPart;
+import org.glassfish.jersey.media.multipart.MultiPart;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
+import java.io.StringWriter;
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.util.Base64;
@Path("/v2/admin/general-config")
@Component
@@ -51,4 +58,33 @@ public class GeneralConfigurationResource extends AbstractAuthGuardedRestResourc
return ok();
}
+
+ @GET
+ @Path("keys")
+ @Produces("multipart/mixed")
+ @PreAuthorize(AuthConstants.IS_ADMIN_ROLE)
+ public Response generateKeyPair() throws Exception {
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
+ kpg.initialize(2048);
+ KeyPair keyPair = kpg.genKeyPair();
+
+ String publicKeyPem = exportKeyAsPem(keyPair.getPublic(), "PUBLIC");
+ String privateKeyPem = exportKeyAsPem(keyPair.getPrivate(), "PRIVATE");
+
+ MultiPart multiPartEntity = new MultiPart()
+ .bodyPart(new BodyPart().entity(publicKeyPem))
+ .bodyPart(new BodyPart().entity(privateKeyPem));
+
+ return Response.ok(multiPartEntity).build();
+ }
+
+ private String exportKeyAsPem(Key key, String keyType) throws Exception {
+ StringWriter sw = new StringWriter();
+
+ sw.write("-----BEGIN " + keyType + " KEY-----\n");
+ sw.write(Base64.getEncoder().encodeToString(key.getEncoded()));
+ sw.write("\n-----END " + keyType + " KEY-----\n");
+
+ return sw.toString();
+ }
}
diff --git a/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenGenerator.java b/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenGenerator.java
new file mode 100644
index 000000000..0424cd02c
--- /dev/null
+++ b/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenGenerator.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.streampipes.security.jwt;
+
+import io.jsonwebtoken.JwtBuilder;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.security.Keys;
+
+import javax.crypto.SecretKey;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Date;
+import java.util.Map;
+
+public class JwtTokenGenerator {
+
+ public static String makeJwtToken(String subject,
+ String tokenSecret,
+ Date expirationDate) {
+
+ return prepareJwtToken(subject, makeHmacKey(tokenSecret), expirationDate).compact();
+
+ }
+
+ public static String makeJwtToken(String subject,
+ Path keyFilePath,
+ Map<String, Object> claims,
+ Date expirationDate) throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
+
+ JwtBuilder builder = prepareJwtToken(subject, makeRsaKey(keyFilePath), expirationDate);
+
+ return builder.addClaims(claims).compact();
+ }
+
+ public static String makeJwtToken(String subject,
+ String tokenSecret,
+ Map<String, Object> claims,
+ Date expirationDate) {
+
+ JwtBuilder builder = prepareJwtToken(subject, makeHmacKey(tokenSecret), expirationDate);
+
+ return builder.addClaims(claims).compact();
+ }
+
+ private static SecretKey makeHmacKey(String tokenSecret) {
+ return Keys.hmacShaKeyFor(tokenSecret.getBytes(StandardCharsets.UTF_8));
+ }
+
+ private static RSAPrivateKey makeRsaKey(Path keyFilePath) throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
+ String key = Files.readString(keyFilePath, Charset.defaultCharset());
+
+ byte[] decoded = KeyUtils.extractPrivate(key);
+
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decoded);
+ return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
+ }
+
+ private static JwtBuilder prepareJwtToken(String subject,
+ Key key,
+ Date expirationDate) {
+ return Jwts
+ .builder()
+ .setSubject(subject)
+ .setIssuedAt(new Date())
+ .setExpiration(expirationDate)
+ .signWith(key);
+ }
+}
diff --git a/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenUtils.java b/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenUtils.java
index beafb4be2..6163c61d1 100644
--- a/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenUtils.java
+++ b/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenUtils.java
@@ -17,52 +17,15 @@
*/
package org.apache.streampipes.security.jwt;
-import io.jsonwebtoken.*;
-import io.jsonwebtoken.security.Keys;
-import io.jsonwebtoken.security.WeakKeyException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.JwtParser;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SigningKeyResolver;
-import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
-import java.util.Date;
-import java.util.Map;
public class JwtTokenUtils {
- private static final Logger LOG = LoggerFactory.getLogger(JwtTokenUtils.class);
-
- public static String makeJwtToken(String subject,
- String tokenSecret,
- Date expirationDate) {
-
- return prepareJwtToken(subject, tokenSecret, expirationDate).compact();
-
- }
-
- public static String makeJwtToken(String subject,
- String tokenSecret,
- Map<String, Object> claims,
- Date expirationDate) {
-
- JwtBuilder builder = prepareJwtToken(subject, tokenSecret, expirationDate);
-
- return builder.addClaims(claims).compact();
- }
-
- private static JwtBuilder prepareJwtToken(String subject,
- String tokenSecret,
- Date expirationDate) {
- SecretKey key = Keys.hmacShaKeyFor(tokenSecret.getBytes(StandardCharsets.UTF_8));
-
- return Jwts
- .builder()
- .setSubject(subject)
- .setIssuedAt(new Date())
- .setExpiration(expirationDate)
- .signWith(key);
- }
-
public static String getUserIdFromToken(String tokenSecret,
String token) {
Claims claims = jwtParser(tokenSecret).parseClaimsJws(token).getBody();
@@ -74,44 +37,16 @@ public class JwtTokenUtils {
return jwtParser(resolver).parseClaimsJws(token).getBody().getSubject();
}
- private static JwtParser jwtParser(String tokenSecret) {
+ public static JwtParser jwtParser(String tokenSecret) {
return Jwts.parserBuilder()
.setSigningKey(tokenSecret.getBytes(StandardCharsets.UTF_8))
.build();
}
- private static JwtParser jwtParser(SigningKeyResolver resolver) {
+ public static JwtParser jwtParser(SigningKeyResolver resolver) {
return Jwts.parserBuilder()
.setSigningKeyResolver(resolver)
.build();
}
- public static boolean validateJwtToken(String jwtToken,
- SigningKeyResolver resolver) {
- return validateJwtToken(jwtParser(resolver), jwtToken);
- }
-
- public static boolean validateJwtToken(String tokenSecret,
- String jwtToken) {
- return validateJwtToken(jwtParser(tokenSecret), jwtToken);
- }
-
- private static boolean validateJwtToken(JwtParser parser,
- String jwtToken) {
- try {
- parser.parseClaimsJws(jwtToken);
- return true;
- } catch (MalformedJwtException ex) {
- LOG.error("Invalid JWT token");
- } catch (ExpiredJwtException ex) {
- LOG.error("Expired JWT token");
- } catch (UnsupportedJwtException ex) {
- LOG.error("Unsupported JWT token");
- } catch (IllegalArgumentException ex) {
- LOG.error("JWT claims are empty.");
- } catch (WeakKeyException ex) {
- LOG.error("Weak Key");
- }
- return false;
- }
}
diff --git a/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenValidator.java b/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenValidator.java
new file mode 100644
index 000000000..9b1237e0d
--- /dev/null
+++ b/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenValidator.java
@@ -0,0 +1,40 @@
+package org.apache.streampipes.security.jwt;
+
+import io.jsonwebtoken.*;
+import io.jsonwebtoken.security.WeakKeyException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class JwtTokenValidator {
+
+ private static final Logger LOG = LoggerFactory.getLogger(JwtTokenValidator.class);
+
+ public static boolean validateJwtToken(String jwtToken,
+ SigningKeyResolver resolver) {
+ return validateJwtToken(JwtTokenUtils.jwtParser(resolver), jwtToken);
+ }
+
+ public static boolean validateJwtToken(String tokenSecret,
+ String jwtToken) {
+ return validateJwtToken(JwtTokenUtils.jwtParser(tokenSecret), jwtToken);
+ }
+
+ private static boolean validateJwtToken(JwtParser parser,
+ String jwtToken) {
+ try {
+ parser.parseClaimsJws(jwtToken);
+ return true;
+ } catch (MalformedJwtException ex) {
+ LOG.error("Invalid JWT token");
+ } catch (ExpiredJwtException ex) {
+ LOG.error("Expired JWT token");
+ } catch (UnsupportedJwtException ex) {
+ LOG.error("Unsupported JWT token");
+ } catch (IllegalArgumentException ex) {
+ LOG.error("JWT claims are empty.");
+ } catch (WeakKeyException ex) {
+ LOG.error("Weak Key");
+ }
+ return false;
+ }
+}
diff --git a/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/KeyGenerator.java b/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/KeyGenerator.java
new file mode 100644
index 000000000..4faa39577
--- /dev/null
+++ b/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/KeyGenerator.java
@@ -0,0 +1,77 @@
+/*
+ * 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.streampipes.security.jwt;
+
+import io.jsonwebtoken.security.Keys;
+import org.apache.streampipes.commons.constants.Envs;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+
+public class KeyGenerator {
+
+ private static Logger LOG = LoggerFactory.getLogger(KeyGenerator.class);
+
+ public Key makeKeyForSecret(String tokenSecret) {
+ return Keys.hmacShaKeyFor(tokenSecret.getBytes(StandardCharsets.UTF_8));
+ }
+
+ public Key makeKeyForSecret(String alg,
+ String tokenSecret) throws IOException {
+ return makeKeyForSecret(alg, tokenSecret, readKey());
+ }
+
+ public Key makeKeyForSecret(String alg,
+ String tokenSecret,
+ String pkContent) {
+ if (alg.equals("RS256")) {
+ try {
+ return makeKeyForRsa(pkContent);
+ } catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ return makeKeyForSecret(tokenSecret);
+ }
+ } else {
+ LOG.warn("Could not properly create the provided key, defaulting to an HMAC token, which will most certainly lead to problems");
+ return makeKeyForSecret(tokenSecret);
+ }
+ }
+
+ public String readKey() throws IOException {
+ return Files.readString(Paths.get(Envs.SP_JWT_PUBLIC_KEY_LOC.getValue()), Charset.defaultCharset());
+ }
+
+ public Key makeKeyForRsa(String key) throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
+ byte[] decoded = KeyUtils.extractPublic(key);
+
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decoded);
+ return keyFactory.generatePublic(keySpec);
+ }
+}
diff --git a/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/KeyUtils.java b/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/KeyUtils.java
new file mode 100644
index 000000000..2cf49b107
--- /dev/null
+++ b/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/KeyUtils.java
@@ -0,0 +1,42 @@
+/*
+ * 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.streampipes.security.jwt;
+
+import org.apache.commons.codec.binary.Base64;
+
+public class KeyUtils {
+
+ public static byte[] extractPrivate(String key) {
+ return decode(key
+ .replace("-----BEGIN PRIVATE KEY-----", "")
+ .replaceAll(System.lineSeparator(), "")
+ .replace("-----END PRIVATE KEY-----", ""));
+ }
+
+ public static byte[] extractPublic(String key) {
+ return decode(key
+ .replace("-----BEGIN PUBLIC KEY-----", "")
+ .replaceAll(System.lineSeparator(), "")
+ .replace("-----END PUBLIC KEY-----", ""));
+ }
+
+ private static byte[] decode(String key) {
+ return Base64.decodeBase64(key);
+ }
+}
diff --git a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/JwtTokenProvider.java b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/JwtTokenProvider.java
index 4cba6f804..779129a46 100644
--- a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/JwtTokenProvider.java
+++ b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/JwtTokenProvider.java
@@ -18,17 +18,28 @@
package org.apache.streampipes.user.management.jwt;
+import org.apache.streampipes.commons.constants.Envs;
import org.apache.streampipes.config.backend.BackendConfig;
+import org.apache.streampipes.config.backend.model.JwtSigningMode;
import org.apache.streampipes.config.backend.model.LocalAuthConfig;
import org.apache.streampipes.model.client.user.Principal;
import org.apache.streampipes.model.client.user.UserAccount;
+import org.apache.streampipes.security.jwt.JwtTokenGenerator;
import org.apache.streampipes.security.jwt.JwtTokenUtils;
+import org.apache.streampipes.security.jwt.JwtTokenValidator;
import org.apache.streampipes.user.management.model.PrincipalUserDetails;
import org.apache.streampipes.user.management.util.GrantedAuthoritiesBuilder;
import org.apache.streampipes.user.management.util.UserInfoUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@@ -37,6 +48,8 @@ import java.util.stream.Collectors;
public class JwtTokenProvider {
+ private static final Logger LOG = LoggerFactory.getLogger(JwtTokenProvider.class);
+
public static final String CLAIM_USER = "user";
private BackendConfig config;
@@ -67,7 +80,16 @@ public class JwtTokenProvider {
Date tokenExpirationDate = makeExpirationDate();
Map<String, Object> claims = makeClaims(userPrincipal, roles);
- return JwtTokenUtils.makeJwtToken(userPrincipal.getUsername(), tokenSecret(), claims, tokenExpirationDate);
+ if (authConfig().getJwtSigningMode() == JwtSigningMode.HMAC) {
+ return JwtTokenGenerator.makeJwtToken(userPrincipal.getUsername(), tokenSecret(), claims, tokenExpirationDate);
+ } else {
+ try {
+ return JwtTokenGenerator.makeJwtToken(userPrincipal.getUsername(), getKeyFilePath(), claims, tokenExpirationDate);
+ } catch (NoSuchAlgorithmException | IOException | InvalidKeySpecException e) {
+ LOG.warn("Could not create JWT token from private key location..defaulting to HMAC");
+ return JwtTokenGenerator.makeJwtToken(userPrincipal.getUsername(), tokenSecret(), claims, tokenExpirationDate);
+ }
+ }
}
private Map<String, Object> makeClaims(Principal principal,
@@ -83,18 +105,22 @@ public class JwtTokenProvider {
}
public boolean validateJwtToken(String jwtToken) {
- return JwtTokenUtils.validateJwtToken(jwtToken, new SpKeyResolver(tokenSecret()));
+ return JwtTokenValidator.validateJwtToken(jwtToken, new SpKeyResolver(tokenSecret()));
}
public boolean validateJwtToken(String tokenSecret,
String jwtToken) {
- return JwtTokenUtils.validateJwtToken(tokenSecret, jwtToken);
+ return JwtTokenValidator.validateJwtToken(tokenSecret, jwtToken);
}
private String tokenSecret() {
return authConfig().getTokenSecret();
}
+ private Path getKeyFilePath() {
+ return Paths.get(Envs.SP_JWT_PRIVATE_KEY_LOC.getValue());
+ }
+
private LocalAuthConfig authConfig() {
return this.config.getLocalAuthConfig();
}
diff --git a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/SpKeyResolver.java b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/SpKeyResolver.java
index 24bbaea8e..99f9ea748 100644
--- a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/SpKeyResolver.java
+++ b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/SpKeyResolver.java
@@ -20,21 +20,21 @@ package org.apache.streampipes.user.management.jwt;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.SigningKeyResolver;
-import io.jsonwebtoken.security.Keys;
+import org.apache.streampipes.config.backend.BackendConfig;
import org.apache.streampipes.model.client.user.Principal;
import org.apache.streampipes.model.client.user.ServiceAccount;
import org.apache.streampipes.model.client.user.UserAccount;
+import org.apache.streampipes.security.jwt.KeyGenerator;
import org.apache.streampipes.storage.api.IUserStorage;
import org.apache.streampipes.storage.management.StorageDispatcher;
import org.apache.streampipes.user.management.encryption.SecretEncryptionManager;
-import java.nio.charset.StandardCharsets;
import java.security.Key;
public class SpKeyResolver implements SigningKeyResolver {
- private String tokenSecret;
- private IUserStorage userStorage;
+ private final String tokenSecret;
+ private final IUserStorage userStorage;
public SpKeyResolver(String tokenSecret) {
this.tokenSecret = tokenSecret;
@@ -48,10 +48,10 @@ public class SpKeyResolver implements SigningKeyResolver {
if (principal == null) {
return null;
} else if (isRealUser(principal)) {
- return makeKeyForSecret(this.tokenSecret);
+ return new KeyGenerator().makeKeyForSecret(jwsHeader.getAlgorithm(), this.tokenSecret, getPublicKeyFromConfig());
} else {
String decryptedSecret = SecretEncryptionManager.decrypt(((ServiceAccount) principal).getClientSecret());
- return makeKeyForSecret(decryptedSecret);
+ return new KeyGenerator().makeKeyForSecret(jwsHeader.getAlgorithm(), decryptedSecret, getPublicKeyFromConfig());
}
}
@@ -68,7 +68,9 @@ public class SpKeyResolver implements SigningKeyResolver {
return principal instanceof UserAccount;
}
- private Key makeKeyForSecret(String tokenSecret) {
- return Keys.hmacShaKeyFor(tokenSecret.getBytes(StandardCharsets.UTF_8));
+ public String getPublicKeyFromConfig() {
+ return BackendConfig.INSTANCE.getLocalAuthConfig().getPublicKey();
}
+
+
}
diff --git a/ui/src/app/configuration/configuration.module.ts b/ui/src/app/configuration/configuration.module.ts
index 969afcade..864d91863 100644
--- a/ui/src/app/configuration/configuration.module.ts
+++ b/ui/src/app/configuration/configuration.module.ts
@@ -53,50 +53,54 @@ import { SecurityUserGroupConfigComponent } from './security-configuration/user-
import { EditGroupDialogComponent } from './security-configuration/edit-group-dialog/edit-group-dialog.component';
import { EmailConfigurationComponent } from './email-configuration/email-configuration.component';
import { GeneralConfigurationComponent } from './general-configuration/general-configuration.component';
+import {
+ SecurityAuthenticationConfigurationComponent
+} from './security-configuration/authentication-configuration/authentication-configuration.component';
@NgModule({
- imports: [
- CommonModule,
- CustomMaterialModule,
- FlexLayoutModule,
- MatGridListModule,
- MatButtonModule,
- MatProgressSpinnerModule,
- MatIconModule,
- MatInputModule,
- MatCheckboxModule,
- MatDividerModule,
- MatTooltipModule,
- FormsModule,
- DragDropModule,
- CoreUiModule,
- ReactiveFormsModule,
- PlatformServicesModule,
- ],
- declarations: [
- ConfigurationComponent,
- ConsulServiceComponent,
- ConsulConfigsComponent,
- ConsulConfigsTextComponent,
- ConsulConfigsPasswordComponent,
- ConsulConfigsBooleanComponent,
- ConsulConfigsNumberComponent,
- DeleteDatalakeIndexComponent,
- EditUserDialogComponent,
- EditGroupDialogComponent,
- EmailConfigurationComponent,
- GeneralConfigurationComponent,
- PipelineElementConfigurationComponent,
- SecurityConfigurationComponent,
- SecurityUserConfigComponent,
- SecurityUserGroupConfigComponent,
- SecurityServiceConfigComponent,
- MessagingConfigurationComponent,
- DatalakeConfigurationComponent,
- ],
- providers: [
- ConfigurationService,
- ]
+ imports: [
+ CommonModule,
+ CustomMaterialModule,
+ FlexLayoutModule,
+ MatGridListModule,
+ MatButtonModule,
+ MatProgressSpinnerModule,
+ MatIconModule,
+ MatInputModule,
+ MatCheckboxModule,
+ MatDividerModule,
+ MatTooltipModule,
+ FormsModule,
+ DragDropModule,
+ CoreUiModule,
+ ReactiveFormsModule,
+ PlatformServicesModule,
+ ],
+ declarations: [
+ ConfigurationComponent,
+ ConsulServiceComponent,
+ ConsulConfigsComponent,
+ ConsulConfigsTextComponent,
+ ConsulConfigsPasswordComponent,
+ ConsulConfigsBooleanComponent,
+ ConsulConfigsNumberComponent,
+ DeleteDatalakeIndexComponent,
+ EditUserDialogComponent,
+ EditGroupDialogComponent,
+ EmailConfigurationComponent,
+ GeneralConfigurationComponent,
+ PipelineElementConfigurationComponent,
+ SecurityAuthenticationConfigurationComponent,
+ SecurityConfigurationComponent,
+ SecurityUserConfigComponent,
+ SecurityUserGroupConfigComponent,
+ SecurityServiceConfigComponent,
+ MessagingConfigurationComponent,
+ DatalakeConfigurationComponent,
+ ],
+ providers: [
+ ConfigurationService,
+ ]
})
export class ConfigurationModule {
}
diff --git a/ui/src/app/configuration/security-configuration/authentication-configuration/authentication-configuration.component.html b/ui/src/app/configuration/security-configuration/authentication-configuration/authentication-configuration.component.html
new file mode 100644
index 000000000..d671fe2b8
--- /dev/null
+++ b/ui/src/app/configuration/security-configuration/authentication-configuration/authentication-configuration.component.html
@@ -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.
+ ~
+ -->
+
+<div fxFlex="100" fxLayout="column">
+ <div>
+ <button mat-button
+ mat-raised-button
+ color="accent"
+ (click)="generateKeyPair()">Generate and download new key pair</button>
+ </div>
+</div>
diff --git a/ui/src/app/configuration/security-configuration/authentication-configuration/authentication-configuration.component.scss b/ui/src/app/configuration/security-configuration/authentication-configuration/authentication-configuration.component.scss
new file mode 100644
index 000000000..13cbc4aac
--- /dev/null
+++ b/ui/src/app/configuration/security-configuration/authentication-configuration/authentication-configuration.component.scss
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ *
+ */
diff --git a/ui/src/app/configuration/security-configuration/authentication-configuration/authentication-configuration.component.ts b/ui/src/app/configuration/security-configuration/authentication-configuration/authentication-configuration.component.ts
new file mode 100644
index 000000000..c205335df
--- /dev/null
+++ b/ui/src/app/configuration/security-configuration/authentication-configuration/authentication-configuration.component.ts
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ *
+ */
+
+
+import { Component, OnInit } from '@angular/core';
+import { ConfigurationService } from '../../shared/configuration.service';
+import * as FileSaver from 'file-saver';
+
+@Component({
+ selector: 'sp-authentication-configuration',
+ templateUrl: './authentication-configuration.component.html',
+ styleUrls: ['./authentication-configuration.component.scss']
+})
+export class SecurityAuthenticationConfigurationComponent implements OnInit {
+
+ constructor(private configurationService: ConfigurationService) {
+
+ }
+
+ ngOnInit(): void {
+ }
+
+ generateKeyPair() {
+ this.configurationService.generateKeyPair().subscribe(result => {
+ console.log(result);
+ this.saveKeyfile('public.key', result[0]);
+ this.saveKeyfile('private.pem', result[1]);
+ });
+ }
+
+ saveKeyfile(filename: string,
+ content: string) {
+ const blob = new Blob([content], {type: 'text/plain'});
+ FileSaver.saveAs(blob, filename);
+ }
+
+}
diff --git a/ui/src/app/configuration/security-configuration/security-configuration.component.html b/ui/src/app/configuration/security-configuration/security-configuration.component.html
index 5db6583fb..3afffc9b6 100644
--- a/ui/src/app/configuration/security-configuration/security-configuration.component.html
+++ b/ui/src/app/configuration/security-configuration/security-configuration.component.html
@@ -41,4 +41,11 @@
</sp-split-section>
</div>
<mat-divider></mat-divider>
+ <div fxFlex="100" fxLayout="column" fxLayoutAlign="start start">
+ <sp-split-section title="Authentication"
+ subtitle="Auth & token settings">
+ <div class="subsection-title">JWT Signature</div>
+ <sp-authentication-configuration></sp-authentication-configuration>
+ </sp-split-section>
+ </div>
</div>
diff --git a/ui/src/app/configuration/shared/configuration.service.ts b/ui/src/app/configuration/shared/configuration.service.ts
index e6dd7795d..fcfbb8e06 100644
--- a/ui/src/app/configuration/shared/configuration.service.ts
+++ b/ui/src/app/configuration/shared/configuration.service.ts
@@ -23,6 +23,7 @@ import { map } from 'rxjs/operators';
import { StreampipesPeContainer } from './streampipes-pe-container.model';
import { MessagingSettings } from './messaging-settings.model';
+import { MultipartUtils } from "./multipart-utils";
@Injectable()
export class ConfigurationService {
@@ -35,6 +36,13 @@ export class ConfigurationService {
return '/streampipes-backend';
}
+ generateKeyPair(): Observable<string[]> {
+ return this.http.get(this.getServerUrl() + '/api/v2/admin/general-config/keys', {responseType: 'text', observe: 'response'})
+ .pipe(map(response => {
+ return new MultipartUtils().extractMultipartPlainTextContent(response);
+ }));
+ }
+
getMessagingSettings(): Observable<MessagingSettings> {
return this.http.get(this.getServerUrl() + '/api/v2/consul/messaging')
.pipe(
@@ -72,7 +80,6 @@ export class ConfigurationService {
return this.http.post(this.getServerUrl() + '/api/v2/consul/messaging', messagingSettings);
}
-
adjustConfigurationKey(consulKey) {
const removedKey = consulKey.substr(consulKey.lastIndexOf('/') + 1, consulKey.length);
diff --git a/ui/src/app/configuration/shared/multipart-utils.ts b/ui/src/app/configuration/shared/multipart-utils.ts
new file mode 100644
index 000000000..2a46b7942
--- /dev/null
+++ b/ui/src/app/configuration/shared/multipart-utils.ts
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ *
+ */
+
+import { HttpResponse } from '@angular/common/http';
+
+export class MultipartUtils {
+
+ extractMultipartPlainTextContent(resp: HttpResponse<string>): string[] {
+
+ const boundary = this.getBoundary(resp);
+ const parts = resp.body.split(boundary);
+ parts.shift();
+ parts.pop();
+ return parts.map(part => part.replace('Content-Type: text/plain', ''));
+ }
+
+ getBoundary(resp: HttpResponse<string>) {
+ return '--' + this.getContentType(resp).split(';')[1].replace('boundary=', '');
+ }
+
+ getContentType(resp: HttpResponse<string>) {
+ return resp.headers.get('Content-type');
+ }
+}