jwt settings install and upgrade
This commit is contained in:
parent
dc07e9ae45
commit
9b519d33a1
@ -18,7 +18,6 @@ package org.thingsboard.server.config;
|
|||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.RandomStringUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@ -29,15 +28,13 @@ import org.thingsboard.server.common.data.security.model.JwtToken;
|
|||||||
import org.thingsboard.server.dao.settings.AdminSettingsService;
|
import org.thingsboard.server.dao.settings.AdminSettingsService;
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Base64;
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@ConfigurationProperties(prefix = "security.jwt")
|
@ConfigurationProperties(prefix = "security.jwt")
|
||||||
@Data
|
@Data
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class JwtSettings {
|
public class JwtSettings {
|
||||||
static final String ADMIN_SETTINGS_JWT_KEY = "jwt";
|
public static final String ADMIN_SETTINGS_JWT_KEY = "jwt";
|
||||||
static final String TOKEN_SIGNING_KEY_DEFAULT = "thingsboardDefaultSigningKey";
|
static final String TOKEN_SIGNING_KEY_DEFAULT = "thingsboardDefaultSigningKey";
|
||||||
/**
|
/**
|
||||||
* {@link JwtToken} will expire after this time.
|
* {@link JwtToken} will expire after this time.
|
||||||
@ -67,25 +64,22 @@ public class JwtSettings {
|
|||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
AdminSettings adminJwtSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, ADMIN_SETTINGS_JWT_KEY);
|
AdminSettings adminJwtSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, ADMIN_SETTINGS_JWT_KEY);
|
||||||
if (adminJwtSettings == null) {
|
if (adminJwtSettings != null) {
|
||||||
if (TOKEN_SIGNING_KEY_DEFAULT.equals(tokenSigningKey)) {
|
log.debug("Loading the JWT admin settings from database");
|
||||||
log.warn("JWT token signing key is default. Generating a new random key");
|
|
||||||
tokenSigningKey = Base64.getEncoder().encodeToString(RandomStringUtils.randomAlphanumeric(64).getBytes(StandardCharsets.UTF_8));
|
|
||||||
}
|
|
||||||
adminJwtSettings = new AdminSettings();
|
|
||||||
adminJwtSettings.setTenantId(TenantId.SYS_TENANT_ID);
|
|
||||||
adminJwtSettings.setKey(ADMIN_SETTINGS_JWT_KEY);
|
|
||||||
adminJwtSettings.setJsonValue(JacksonUtil.valueToTree(this));
|
|
||||||
log.info("Saving new JWT admin settings. From this moment, the JWT parameters from YAML and ENV will be ignored");
|
|
||||||
adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminJwtSettings);
|
|
||||||
} else {
|
|
||||||
log.debug("Loading the JWT admin settings");
|
|
||||||
JwtSettings jwtSettings = JacksonUtil.treeToValue(adminJwtSettings.getJsonValue(), JwtSettings.class);
|
JwtSettings jwtSettings = JacksonUtil.treeToValue(adminJwtSettings.getJsonValue(), JwtSettings.class);
|
||||||
this.setRefreshTokenExpTime(jwtSettings.getRefreshTokenExpTime());
|
this.setRefreshTokenExpTime(jwtSettings.getRefreshTokenExpTime());
|
||||||
this.setTokenExpirationTime(jwtSettings.getTokenExpirationTime());
|
this.setTokenExpirationTime(jwtSettings.getTokenExpirationTime());
|
||||||
this.setTokenIssuer(jwtSettings.getTokenIssuer());
|
this.setTokenIssuer(jwtSettings.getTokenIssuer());
|
||||||
this.setTokenSigningKey(jwtSettings.getTokenSigningKey());
|
this.setTokenSigningKey(jwtSettings.getTokenSigningKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasDefaultTokenSigningKey()) {
|
||||||
|
log.warn("JWT token signing key is default. This is a security issue. Please, consider to set unique value");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasDefaultTokenSigningKey() {
|
||||||
|
return TOKEN_SIGNING_KEY_DEFAULT.equals(tokenSigningKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import org.thingsboard.server.service.install.migrate.EntitiesMigrateService;
|
|||||||
import org.thingsboard.server.service.install.migrate.TsLatestMigrateService;
|
import org.thingsboard.server.service.install.migrate.TsLatestMigrateService;
|
||||||
import org.thingsboard.server.service.install.update.CacheCleanupService;
|
import org.thingsboard.server.service.install.update.CacheCleanupService;
|
||||||
import org.thingsboard.server.service.install.update.DataUpdateService;
|
import org.thingsboard.server.service.install.update.DataUpdateService;
|
||||||
|
import org.thingsboard.server.service.install.ConditionValidatorUpgradeService;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Profile("install")
|
@Profile("install")
|
||||||
@ -84,11 +85,17 @@ public class ThingsboardInstallService {
|
|||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
private TsLatestMigrateService latestMigrateService;
|
private TsLatestMigrateService latestMigrateService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ConditionValidatorUpgradeService conditionValidatorUpgradeService;
|
||||||
|
|
||||||
|
|
||||||
public void performInstall() {
|
public void performInstall() {
|
||||||
try {
|
try {
|
||||||
if (isUpgrade) {
|
if (isUpgrade) {
|
||||||
log.info("Starting ThingsBoard Upgrade from version {} ...", upgradeFromVersion);
|
log.info("Starting ThingsBoard Upgrade from version {} ...", upgradeFromVersion);
|
||||||
|
|
||||||
|
conditionValidatorUpgradeService.validateConditionsBeforeUpgrade(upgradeFromVersion);
|
||||||
|
|
||||||
cacheCleanupService.clearCache(upgradeFromVersion);
|
cacheCleanupService.clearCache(upgradeFromVersion);
|
||||||
|
|
||||||
if ("2.5.0-cassandra".equals(upgradeFromVersion)) {
|
if ("2.5.0-cassandra".equals(upgradeFromVersion)) {
|
||||||
@ -224,6 +231,10 @@ public class ThingsboardInstallService {
|
|||||||
log.info("Updating system data...");
|
log.info("Updating system data...");
|
||||||
systemDataLoaderService.updateSystemWidgets();
|
systemDataLoaderService.updateSystemWidgets();
|
||||||
break;
|
break;
|
||||||
|
case "3.4.0":
|
||||||
|
log.info("Upgrading ThingsBoard from version 3.4.0 to 3.5.0 ...");
|
||||||
|
dataUpdateService.updateData("3.4.0");
|
||||||
|
break;
|
||||||
|
|
||||||
//TODO update CacheCleanupService on the next version upgrade
|
//TODO update CacheCleanupService on the next version upgrade
|
||||||
|
|
||||||
@ -257,6 +268,7 @@ public class ThingsboardInstallService {
|
|||||||
systemDataLoaderService.createSysAdmin();
|
systemDataLoaderService.createSysAdmin();
|
||||||
systemDataLoaderService.createDefaultTenantProfiles();
|
systemDataLoaderService.createDefaultTenantProfiles();
|
||||||
systemDataLoaderService.createAdminSettings();
|
systemDataLoaderService.createAdminSettings();
|
||||||
|
systemDataLoaderService.createJwtAdminSettings();
|
||||||
systemDataLoaderService.loadSystemWidgets();
|
systemDataLoaderService.loadSystemWidgets();
|
||||||
systemDataLoaderService.createOAuth2Templates();
|
systemDataLoaderService.createOAuth2Templates();
|
||||||
systemDataLoaderService.createQueues();
|
systemDataLoaderService.createQueues();
|
||||||
|
|||||||
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright © 2016-2022 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.service.install;
|
||||||
|
|
||||||
|
public interface ConditionValidatorUpgradeService {
|
||||||
|
|
||||||
|
void validateConditionsBeforeUpgrade(String fromVersion) throws Exception;
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* Copyright © 2016-2022 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.service.install;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.annotation.Profile;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.thingsboard.server.common.data.AdminSettings;
|
||||||
|
import org.thingsboard.server.common.data.id.TenantId;
|
||||||
|
import org.thingsboard.server.config.JwtSettings;
|
||||||
|
import org.thingsboard.server.dao.settings.AdminSettingsService;
|
||||||
|
|
||||||
|
import javax.validation.ValidationException;
|
||||||
|
|
||||||
|
import static org.thingsboard.server.config.JwtSettings.ADMIN_SETTINGS_JWT_KEY;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Profile("install")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class ConditionValidatorUpgradeServiceImpl implements ConditionValidatorUpgradeService {
|
||||||
|
|
||||||
|
private final AdminSettingsService adminSettingsService;
|
||||||
|
|
||||||
|
private final JwtSettings jwtSettings;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateConditionsBeforeUpgrade(String fromVersion) throws Exception {
|
||||||
|
log.info("Validating conditions before upgrade..");
|
||||||
|
validateJwtTokenSigningKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
void validateJwtTokenSigningKey() {
|
||||||
|
AdminSettings adminJwtSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, ADMIN_SETTINGS_JWT_KEY);
|
||||||
|
if (adminJwtSettings == null) {
|
||||||
|
if (jwtSettings.hasDefaultTokenSigningKey()) {
|
||||||
|
String allowDefaultJwtSigningKey = System.getenv("TB_ALLOW_DEFAULT_JWT_SIGNING_KEY");
|
||||||
|
if ("true".equalsIgnoreCase(allowDefaultJwtSigningKey)) {
|
||||||
|
log.warn("Default JWT signing key is allowed. This is a security issue. Please, consider to set a strong key in admin settings");
|
||||||
|
} else {
|
||||||
|
String message = "Please, set a unique signing key with env variable JWT_TOKEN_SIGNING_KEY. Key is a Base64 encoded phrase. This will require to generate new tokens for all users and API that uses JWT tokens. To allow insecure JWS use TB_ALLOW_DEFAULT_JWT_SIGNING_KEY=true";
|
||||||
|
log.error(message);
|
||||||
|
throw new ValidationException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -22,6 +22,7 @@ import com.google.common.util.concurrent.Futures;
|
|||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@ -29,6 +30,7 @@ import org.springframework.context.annotation.Lazy;
|
|||||||
import org.springframework.context.annotation.Profile;
|
import org.springframework.context.annotation.Profile;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.thingsboard.common.util.JacksonUtil;
|
||||||
import org.thingsboard.common.util.ThingsBoardThreadFactory;
|
import org.thingsboard.common.util.ThingsBoardThreadFactory;
|
||||||
import org.thingsboard.server.common.data.AdminSettings;
|
import org.thingsboard.server.common.data.AdminSettings;
|
||||||
import org.thingsboard.server.common.data.Customer;
|
import org.thingsboard.server.common.data.Customer;
|
||||||
@ -82,6 +84,7 @@ import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileCon
|
|||||||
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
|
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
|
||||||
import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration;
|
import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration;
|
||||||
import org.thingsboard.server.common.data.widget.WidgetsBundle;
|
import org.thingsboard.server.common.data.widget.WidgetsBundle;
|
||||||
|
import org.thingsboard.server.config.JwtSettings;
|
||||||
import org.thingsboard.server.dao.attributes.AttributesService;
|
import org.thingsboard.server.dao.attributes.AttributesService;
|
||||||
import org.thingsboard.server.dao.customer.CustomerService;
|
import org.thingsboard.server.dao.customer.CustomerService;
|
||||||
import org.thingsboard.server.dao.device.DeviceCredentialsService;
|
import org.thingsboard.server.dao.device.DeviceCredentialsService;
|
||||||
@ -100,13 +103,18 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import javax.annotation.PreDestroy;
|
import javax.annotation.PreDestroy;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import static org.thingsboard.server.config.JwtSettings.ADMIN_SETTINGS_JWT_KEY;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Profile("install")
|
@Profile("install")
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ -167,6 +175,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private QueueService queueService;
|
private QueueService queueService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JwtSettings jwtSettings;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
protected BCryptPasswordEncoder passwordEncoder() {
|
protected BCryptPasswordEncoder passwordEncoder() {
|
||||||
return new BCryptPasswordEncoder();
|
return new BCryptPasswordEncoder();
|
||||||
@ -656,4 +667,26 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
|
|||||||
queueService.saveQueue(sequentialByOriginatorQueue);
|
queueService.saveQueue(sequentialByOriginatorQueue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createJwtAdminSettings() throws Exception {
|
||||||
|
Objects.requireNonNull(jwtSettings,"JWT settings is null");
|
||||||
|
AdminSettings adminJwtSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, ADMIN_SETTINGS_JWT_KEY);
|
||||||
|
if (adminJwtSettings == null) {
|
||||||
|
if (jwtSettings.hasDefaultTokenSigningKey()) {
|
||||||
|
String allowDefaultJwtSigningKey = System.getenv("TB_ALLOW_DEFAULT_JWT_SIGNING_KEY");
|
||||||
|
if (!"true".equalsIgnoreCase(allowDefaultJwtSigningKey)) {
|
||||||
|
log.warn("JWT token signing key is default. Generating a new random key");
|
||||||
|
jwtSettings.setTokenSigningKey(Base64.getEncoder().encodeToString(RandomStringUtils.randomAlphanumeric(64).getBytes(StandardCharsets.UTF_8)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
adminJwtSettings = new AdminSettings();
|
||||||
|
adminJwtSettings.setTenantId(TenantId.SYS_TENANT_ID);
|
||||||
|
adminJwtSettings.setKey(ADMIN_SETTINGS_JWT_KEY);
|
||||||
|
adminJwtSettings.setJsonValue(JacksonUtil.valueToTree(jwtSettings));
|
||||||
|
log.info("Saving new JWT admin settings. From this moment, the JWT parameters from YAML and ENV will be ignored");
|
||||||
|
adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminJwtSettings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,8 @@ public interface SystemDataLoaderService {
|
|||||||
|
|
||||||
void createAdminSettings() throws Exception;
|
void createAdminSettings() throws Exception;
|
||||||
|
|
||||||
|
void createJwtAdminSettings() throws Exception;
|
||||||
|
|
||||||
void createOAuth2Templates() throws Exception;
|
void createOAuth2Templates() throws Exception;
|
||||||
|
|
||||||
void loadSystemWidgets() throws Exception;
|
void loadSystemWidgets() throws Exception;
|
||||||
|
|||||||
@ -159,6 +159,10 @@ public class DefaultDataUpdateService implements DataUpdateService {
|
|||||||
tenantsProfileQueueConfigurationUpdater.updateEntities();
|
tenantsProfileQueueConfigurationUpdater.updateEntities();
|
||||||
rateLimitsUpdater.updateEntities();
|
rateLimitsUpdater.updateEntities();
|
||||||
break;
|
break;
|
||||||
|
case "3.4.0":
|
||||||
|
log.info("Updating data from version 3.4.0 to 3.5.0 ...");
|
||||||
|
systemDataLoaderService.createJwtAdminSettings();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion);
|
throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -107,7 +107,7 @@ plugins:
|
|||||||
# Security parameters
|
# Security parameters
|
||||||
security:
|
security:
|
||||||
# JWT Token parameters
|
# JWT Token parameters
|
||||||
jwt:
|
jwt: # Since 3.5.0 values are persisted to the database during install or upgrade. On Install, the key will be generated randomly if no custom value set.
|
||||||
tokenExpirationTime: "${JWT_TOKEN_EXPIRATION_TIME:9000}" # Number of seconds (2.5 hours)
|
tokenExpirationTime: "${JWT_TOKEN_EXPIRATION_TIME:9000}" # Number of seconds (2.5 hours)
|
||||||
refreshTokenExpTime: "${JWT_REFRESH_TOKEN_EXPIRATION_TIME:604800}" # Number of seconds (1 week)
|
refreshTokenExpTime: "${JWT_REFRESH_TOKEN_EXPIRATION_TIME:604800}" # Number of seconds (1 week)
|
||||||
tokenIssuer: "${JWT_TOKEN_ISSUER:thingsboard.io}"
|
tokenIssuer: "${JWT_TOKEN_ISSUER:thingsboard.io}"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user