Use SSL credentials configuration to setup HTTPS. Enable Lwm2m credentials by default.

This commit is contained in:
Igor Kulikov 2021-10-27 14:19:50 +03:00
parent 330ff09437
commit 483a69fed1
12 changed files with 226 additions and 66 deletions

View File

@ -31,6 +31,7 @@ import java.util.Arrays;
"org.thingsboard.server.service.install",
"org.thingsboard.server.dao",
"org.thingsboard.server.common.stats",
"org.thingsboard.server.common.transport.config.ssl",
"org.thingsboard.server.cache"})
public class ThingsboardInstallApplication {

View File

@ -23,14 +23,30 @@ server:
ssl:
# Enable/disable SSL support
enabled: "${SSL_ENABLED:false}"
# Path to the key store that holds the SSL certificate
key-store: "${SSL_KEY_STORE:classpath:keystore/keystore.p12}"
# Password used to access the key store
key-store-password: "${SSL_KEY_STORE_PASSWORD:thingsboard}"
# Type of the key store
key-store-type: "${SSL_KEY_STORE_TYPE:PKCS12}"
# Alias that identifies the key in the key store
key-alias: "${SSL_KEY_ALIAS:tomcat}"
# Server SSL credentials
credentials:
# Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore)
type: "${SSL_CREDENTIALS_TYPE:PEM}"
# PEM server credentials
pem:
# Path to the server certificate file (holds server certificate or certificate chain, may include server private key)
cert_file: "${SSL_PEM_CERT:server.pem}"
# Path to the server certificate private key file (optional)
key_file: "${SSL_PEM_KEY:server_key.pem}"
# Server certificate private key password (optional)
key_password: "${SSL_PEM_KEY_PASSWORD:server_key_password}"
# Keystore server credentials
keystore:
# Type of the key store
type: "${SSL_KEY_STORE_TYPE:PKCS12}"
# Path to the key store that holds the SSL certificate
store_file: "${SSL_KEY_STORE:classpath:keystore/keystore.p12}"
# Password used to access the key store
store_password: "${SSL_KEY_STORE_PASSWORD:thingsboard}"
# Key alias
key_alias: "${SSL_KEY_ALIAS:tomcat}"
# Password used to access the key
key_password: "${SSL_KEY_PASSWORD:thingsboard}"
log_controller_error_stack_trace: "${HTTP_LOG_CONTROLLER_ERROR_STACK_TRACE:false}"
ws:
send_timeout: "${TB_SERVER_WS_SEND_TIMEOUT:5000}"
@ -679,10 +695,10 @@ transport:
store_file: "${COAP_DTLS_KEY_STORE:coapserver.jks}"
# Password used to access the key store
store_password: "${COAP_DTLS_KEY_STORE_PASSWORD:server_ks_password}"
# Password used to access the key
key_password: "${COAP_DTLS_KEY_PASSWORD:server_key_password}"
# Key alias
key_alias: "${COAP_DTLS_KEY_ALIAS:serveralias}"
# Password used to access the key
key_password: "${COAP_DTLS_KEY_PASSWORD:server_key_password}"
x509:
# Skip certificate validity check for client certificates.
skip_validity_check_for_client_cert: "${TB_COAP_X509_DTLS_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
@ -702,7 +718,7 @@ transport:
# Server X509 Certificates support
credentials:
# Whether to enable LWM2M server X509 Certificate/RPK support
enabled: "${LWM2M_SERVER_CREDENTIALS_ENABLED:false}"
enabled: "${LWM2M_SERVER_CREDENTIALS_ENABLED:true}"
# Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore)
type: "${LWM2M_SERVER_CREDENTIALS_TYPE:PEM}"
# PEM server credentials
@ -721,10 +737,10 @@ transport:
store_file: "${LWM2M_SERVER_KEY_STORE:lwm2mserver.jks}"
# Password used to access the key store
store_password: "${LWM2M_SERVER_KEY_STORE_PASSWORD:server_ks_password}"
# Password used to access the key
key_password: "${LWM2M_SERVER_KEY_PASSWORD:server_key_password}"
# Key alias
key_alias: "${LWM2M_SERVER_KEY_ALIAS:server}"
# Password used to access the key
key_password: "${LWM2M_SERVER_KEY_PASSWORD:server_ks_password}"
# Only Certificate_x509:
skip_validity_check_for_client_cert: "${TB_LWM2M_SERVER_SECURITY_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
bootstrap:
@ -738,7 +754,7 @@ transport:
# Bootstrap server X509 Certificates support
credentials:
# Whether to enable LWM2M bootstrap server X509 Certificate/RPK support
enabled: "${LWM2M_BS_CREDENTIALS_ENABLED:false}"
enabled: "${LWM2M_BS_CREDENTIALS_ENABLED:true}"
# Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore)
type: "${LWM2M_BS_CREDENTIALS_TYPE:PEM}"
# PEM server credentials
@ -757,15 +773,15 @@ transport:
store_file: "${LWM2M_BS_KEY_STORE:lwm2mserver.jks}"
# Password used to access the key store
store_password: "${LWM2M_BS_KEY_STORE_PASSWORD:server_ks_password}"
# Password used to access the key
key_password: "${LWM2M_BS_KEY_PASSWORD:server_key_password}"
# Key alias
key_alias: "${LWM2M_BS_KEY_ALIAS:bootstrap}"
# Password used to access the key
key_password: "${LWM2M_BS_KEY_PASSWORD:server_ks_password}"
security:
# X509 trust certificates
trust-credentials:
# Whether to load X509 trust certificates
enabled: "${LWM2M_TRUST_CREDENTIALS_ENABLED:false}"
enabled: "${LWM2M_TRUST_CREDENTIALS_ENABLED:true}"
# Trust certificates store type (PEM - pem certificates file; KEYSTORE - java keystore)
type: "${LWM2M_TRUST_CREDENTIALS_TYPE:PEM}"
# PEM certificates

View File

@ -27,26 +27,35 @@ import java.net.URL;
@Slf4j
public class ResourceUtils {
public static final String CLASSPATH_URL_PREFIX = "classpath:";
public static boolean resourceExists(Object classLoaderSource, String filePath) {
return resourceExists(classLoaderSource.getClass().getClassLoader(), filePath);
}
public static boolean resourceExists(ClassLoader classLoader, String filePath) {
File resourceFile = new File(filePath);
if (resourceFile.exists()) {
boolean classPathResource = false;
String path = filePath;
if (path.startsWith(CLASSPATH_URL_PREFIX)) {
path = path.substring(CLASSPATH_URL_PREFIX.length());
classPathResource = true;
}
if (!classPathResource) {
File resourceFile = new File(path);
if (resourceFile.exists()) {
return true;
}
}
InputStream classPathStream = classLoader.getResourceAsStream(path);
if (classPathStream != null) {
return true;
} else {
InputStream classPathStream = classLoader.getResourceAsStream(filePath);
if (classPathStream != null) {
return true;
} else {
try {
URL url = Resources.getResource(filePath);
if (url != null) {
return true;
}
} catch (IllegalArgumentException e) {}
}
try {
URL url = Resources.getResource(path);
if (url != null) {
return true;
}
} catch (IllegalArgumentException e) {}
}
return false;
}
@ -56,32 +65,40 @@ public class ResourceUtils {
}
public static InputStream getInputStream(ClassLoader classLoader, String filePath) {
boolean classPathResource = false;
String path = filePath;
if (path.startsWith(CLASSPATH_URL_PREFIX)) {
path = path.substring(CLASSPATH_URL_PREFIX.length());
classPathResource = true;
}
try {
InputStream keyStoreInputStream;
File keyStoreFile = new File(filePath);
if (keyStoreFile.exists()) {
log.info("Reading key store from file {}", filePath);
keyStoreInputStream = new FileInputStream(keyStoreFile);
} else {
InputStream classPathStream = classLoader.getResourceAsStream(filePath);
if (classPathStream != null) {
log.info("Reading key store from class path {}", filePath);
keyStoreInputStream = classPathStream;
} else {
URI uri = Resources.getResource(filePath).toURI();
log.info("Reading key store from URI {}", filePath);
keyStoreInputStream = new FileInputStream(new File(uri));
if (!classPathResource) {
File resourceFile = new File(path);
if (resourceFile.exists()) {
log.info("Reading resource data from file {}", filePath);
return new FileInputStream(resourceFile);
}
}
InputStream classPathStream = classLoader.getResourceAsStream(path);
if (classPathStream != null) {
log.info("Reading resource data from class path {}", filePath);
return classPathStream;
} else {
URL url = Resources.getResource(path);
if (url != null) {
URI uri = url.toURI();
log.info("Reading resource data from URI {}", filePath);
return new FileInputStream(new File(uri));
}
}
return keyStoreInputStream;
} catch (Exception e) {
if (e instanceof NullPointerException) {
log.warn("Unable to find resource: " + filePath);
} else {
log.warn("Unable to find resource: " + filePath, e);
}
throw new RuntimeException("Unable to find resource: " + filePath);
}
throw new RuntimeException("Unable to find resource: " + filePath);
}
public static String getUri(Object classLoaderSource, String filePath) {
@ -90,10 +107,10 @@ public class ResourceUtils {
public static String getUri(ClassLoader classLoader, String filePath) {
try {
File keyStoreFile = new File(filePath);
if (keyStoreFile.exists()) {
log.info("Reading key store from file {}", filePath);
return keyStoreFile.getAbsolutePath();
File resourceFile = new File(filePath);
if (resourceFile.exists()) {
log.info("Reading resource data from file {}", filePath);
return resourceFile.getAbsolutePath();
} else {
URL url = classLoader.getResource(filePath);
return url.toURI().toString();

View File

@ -71,6 +71,7 @@ public abstract class AbstractSslCredentials implements SslCredentials {
String alias = e.nextElement();
privateKeyEntry = tryGetPrivateKeyEntry(this.keyStore, alias, this.keyPasswordArray);
if (privateKeyEntry != null) {
this.updateKeyAlias(alias);
break;
}
}
@ -87,6 +88,11 @@ public abstract class AbstractSslCredentials implements SslCredentials {
}
}
@Override
public KeyStore getKeyStore() {
return this.keyStore;
}
@Override
public PrivateKey getPrivateKey() {
return this.privateKey;
@ -123,12 +129,10 @@ public abstract class AbstractSslCredentials implements SslCredentials {
protected abstract boolean canUse();
protected abstract String getKeyPassword();
protected abstract String getKeyAlias();
protected abstract KeyStore loadKeyStore(boolean isPrivateKeyRequired, char[] keyPasswordArray) throws IOException, GeneralSecurityException;
protected abstract void updateKeyAlias(String keyAlias);
private static X509Certificate[] asX509Certificates(Certificate[] certificates) {
if (null == certificates || 0 == certificates.length) {
throw new IllegalArgumentException("certificates missing!");

View File

@ -49,4 +49,9 @@ public class KeystoreSslCredentials extends AbstractSslCredentials {
}
return keyStore;
}
@Override
protected void updateKeyAlias(String keyAlias) {
this.keyAlias = keyAlias;
}
}

View File

@ -17,6 +17,7 @@ package org.thingsboard.server.common.transport.config.ssl;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
@ -48,10 +49,11 @@ import java.util.stream.Collectors;
@EqualsAndHashCode(callSuper = false)
public class PemSslCredentials extends AbstractSslCredentials {
private static final String DEFAULT_KEY_ALIAS = "server";
private String certFile;
private String keyFile;
private String keyPassword;
private final String keyAlias = "serveralias";
@Override
protected boolean canUse() {
@ -125,8 +127,17 @@ public class PemSslCredentials extends AbstractSslCredentials {
CertPath certPath = factory.generateCertPath(certificates);
List<? extends Certificate> path = certPath.getCertificates();
Certificate[] x509Certificates = path.toArray(new Certificate[0]);
keyStore.setKeyEntry(this.keyAlias, privateKey, keyPasswordArray, x509Certificates);
keyStore.setKeyEntry(DEFAULT_KEY_ALIAS, privateKey, keyPasswordArray, x509Certificates);
}
return keyStore;
}
@Override
public String getKeyAlias() {
return DEFAULT_KEY_ALIAS;
}
@Override
protected void updateKeyAlias(String keyAlias) {
}
}

View File

@ -19,6 +19,7 @@ import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
@ -30,6 +31,12 @@ public interface SslCredentials {
void init(boolean trustsOnly) throws IOException, GeneralSecurityException;
KeyStore getKeyStore();
String getKeyPassword();
String getKeyAlias();
PrivateKey getPrivateKey();
PublicKey getPublicKey();

View File

@ -0,0 +1,71 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed 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.thingsboard.server.common.transport.config.ssl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.server.Ssl;
import org.springframework.boot.web.server.SslStoreProvider;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.security.KeyStore;
@Component
@ConditionalOnExpression("'${spring.main.web-environment:true}'=='true' && '${server.ssl.enabled:false}'=='true'")
public class SslCredentialsWebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Bean
@ConfigurationProperties(prefix = "server.ssl.credentials")
public SslCredentialsConfig httpServerSslCredentials() {
return new SslCredentialsConfig("HTTP Server SSL Credentials", false);
}
@Autowired
@Qualifier("httpServerSslCredentials")
private SslCredentialsConfig httpServerSslCredentialsConfig;
private final ServerProperties serverProperties;
public SslCredentialsWebServerCustomizer(ServerProperties serverProperties) {
this.serverProperties = serverProperties;
}
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
SslCredentials sslCredentials = this.httpServerSslCredentialsConfig.getCredentials();
Ssl ssl = serverProperties.getSsl();
ssl.setKeyAlias(sslCredentials.getKeyAlias());
ssl.setKeyPassword(sslCredentials.getKeyPassword());
factory.setSsl(ssl);
factory.setSslStoreProvider(new SslStoreProvider() {
@Override
public KeyStore getKeyStore() {
return sslCredentials.getKeyStore();
}
@Override
public KeyStore getTrustStore() {
return null;
}
});
}
}

View File

@ -118,10 +118,10 @@ transport:
store_file: "${COAP_DTLS_KEY_STORE:coapserver.jks}"
# Password used to access the key store
store_password: "${COAP_DTLS_KEY_STORE_PASSWORD:server_ks_password}"
# Password used to access the key
key_password: "${COAP_DTLS_KEY_PASSWORD:server_key_password}"
# Key alias
key_alias: "${COAP_DTLS_KEY_ALIAS:serveralias}"
# Password used to access the key
key_password: "${COAP_DTLS_KEY_PASSWORD:server_key_password}"
x509:
# Skip certificate validity check for client certificates.
skip_validity_check_for_client_cert: "${TB_COAP_X509_DTLS_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"

View File

@ -19,6 +19,34 @@ server:
address: "${HTTP_BIND_ADDRESS:0.0.0.0}"
# Server bind port
port: "${HTTP_BIND_PORT:8081}"
# Server SSL configuration
ssl:
# Enable/disable SSL support
enabled: "${SSL_ENABLED:false}"
# Server SSL credentials
credentials:
# Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore)
type: "${SSL_CREDENTIALS_TYPE:PEM}"
# PEM server credentials
pem:
# Path to the server certificate file (holds server certificate or certificate chain, may include server private key)
cert_file: "${SSL_PEM_CERT:server.pem}"
# Path to the server certificate private key file (optional)
key_file: "${SSL_PEM_KEY:server_key.pem}"
# Server certificate private key password (optional)
key_password: "${SSL_PEM_KEY_PASSWORD:server_key_password}"
# Keystore server credentials
keystore:
# Type of the key store
type: "${SSL_KEY_STORE_TYPE:PKCS12}"
# Path to the key store that holds the SSL certificate
store_file: "${SSL_KEY_STORE:classpath:keystore/keystore.p12}"
# Password used to access the key store
store_password: "${SSL_KEY_STORE_PASSWORD:thingsboard}"
# Key alias
key_alias: "${SSL_KEY_ALIAS:tomcat}"
# Password used to access the key
key_password: "${SSL_KEY_PASSWORD:thingsboard}"
# Zookeeper connection parameters. Used for service discovery.
zk:
@ -283,4 +311,4 @@ management:
web:
exposure:
# Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics).
include: '${METRICS_ENDPOINTS_EXPOSE:info}'
include: '${METRICS_ENDPOINTS_EXPOSE:info}'

View File

@ -114,7 +114,7 @@ transport:
# Server X509 Certificates support
credentials:
# Whether to enable LWM2M server X509 Certificate/RPK support
enabled: "${LWM2M_SERVER_CREDENTIALS_ENABLED:false}"
enabled: "${LWM2M_SERVER_CREDENTIALS_ENABLED:true}"
# Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore)
type: "${LWM2M_SERVER_CREDENTIALS_TYPE:PEM}"
# PEM server credentials
@ -133,10 +133,10 @@ transport:
store_file: "${LWM2M_SERVER_KEY_STORE:lwm2mserver.jks}"
# Password used to access the key store
store_password: "${LWM2M_SERVER_KEY_STORE_PASSWORD:server_ks_password}"
# Password used to access the key
key_password: "${LWM2M_SERVER_KEY_PASSWORD:server_key_password}"
# Key alias
key_alias: "${LWM2M_SERVER_KEY_ALIAS:server}"
# Password used to access the key
key_password: "${LWM2M_SERVER_KEY_PASSWORD:server_ks_password}"
# Only Certificate_x509:
skip_validity_check_for_client_cert: "${TB_LWM2M_SERVER_SECURITY_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
bootstrap:
@ -150,7 +150,7 @@ transport:
# Bootstrap server X509 Certificates support
credentials:
# Whether to enable LWM2M bootstrap server X509 Certificate/RPK support
enabled: "${LWM2M_BS_CREDENTIALS_ENABLED:false}"
enabled: "${LWM2M_BS_CREDENTIALS_ENABLED:true}"
# Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore)
type: "${LWM2M_BS_CREDENTIALS_TYPE:PEM}"
# PEM server credentials
@ -169,15 +169,15 @@ transport:
store_file: "${LWM2M_BS_KEY_STORE:lwm2mserver.jks}"
# Password used to access the key store
store_password: "${LWM2M_BS_KEY_STORE_PASSWORD:server_ks_password}"
# Password used to access the key
key_password: "${LWM2M_BS_KEY_PASSWORD:server_key_password}"
# Key alias
key_alias: "${LWM2M_BS_KEY_ALIAS:bootstrap}"
# Password used to access the key
key_password: "${LWM2M_BS_KEY_PASSWORD:server_ks_password}"
security:
# X509 trust certificates
trust-credentials:
# Whether to load X509 trust certificates
enabled: "${LWM2M_TRUST_CREDENTIALS_ENABLED:false}"
enabled: "${LWM2M_TRUST_CREDENTIALS_ENABLED:true}"
# Trust certificates store type (PEM - pem certificates file; KEYSTORE - java keystore)
type: "${LWM2M_TRUST_CREDENTIALS_TYPE:PEM}"
# PEM certificates