diff --git a/application/src/main/java/org/thingsboard/server/config/JwtSettings.java b/application/src/main/java/org/thingsboard/server/config/JwtSettings.java index 14e228bde8..b85f1738c9 100644 --- a/application/src/main/java/org/thingsboard/server/config/JwtSettings.java +++ b/application/src/main/java/org/thingsboard/server/config/JwtSettings.java @@ -15,27 +15,16 @@ */ package org.thingsboard.server.config; -import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.AdminSettings; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.model.JwtToken; -import org.thingsboard.server.dao.settings.AdminSettingsService; - -import javax.annotation.PostConstruct; @Component @ConfigurationProperties(prefix = "security.jwt") @Data -@Slf4j public class JwtSettings { - public static final String ADMIN_SETTINGS_JWT_KEY = "jwt"; - static final String TOKEN_SIGNING_KEY_DEFAULT = "thingsboardDefaultSigningKey"; + /** * {@link JwtToken} will expire after this time. */ @@ -57,29 +46,4 @@ public class JwtSettings { */ private Integer refreshTokenExpTime; - @JsonIgnore - @Autowired - private AdminSettingsService adminSettingsService; - - @PostConstruct - public void init() { - AdminSettings adminJwtSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, ADMIN_SETTINGS_JWT_KEY); - if (adminJwtSettings != null) { - log.debug("Loading the JWT admin settings from database"); - JwtSettings jwtSettings = JacksonUtil.treeToValue(adminJwtSettings.getJsonValue(), JwtSettings.class); - this.setRefreshTokenExpTime(jwtSettings.getRefreshTokenExpTime()); - this.setTokenExpirationTime(jwtSettings.getTokenExpirationTime()); - this.setTokenIssuer(jwtSettings.getTokenIssuer()); - 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); - } - } diff --git a/application/src/main/java/org/thingsboard/server/config/JwtSettingsService.java b/application/src/main/java/org/thingsboard/server/config/JwtSettingsService.java new file mode 100644 index 0000000000..bb7d3abc55 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/config/JwtSettingsService.java @@ -0,0 +1,117 @@ +/** + * 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.config; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.RandomStringUtils; +import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.settings.AdminSettingsService; + +import javax.annotation.PostConstruct; +import javax.validation.ValidationException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Objects; + +@Service +@RequiredArgsConstructor +@Slf4j +public class JwtSettingsService { + + static final String ADMIN_SETTINGS_JWT_KEY = "jwt"; + static final String TOKEN_SIGNING_KEY_DEFAULT = "thingsboardDefaultSigningKey"; + static final String TB_ALLOW_DEFAULT_JWT_SIGNING_KEY = "TB_ALLOW_DEFAULT_JWT_SIGNING_KEY"; + + private final AdminSettingsService adminSettingsService; + + @Getter + private final JwtSettings jwtSettings; + + @PostConstruct + public void init() { + AdminSettings adminJwtSettings = findJwtAdminSettings(); + if (adminJwtSettings != null) { + log.debug("Loading the JWT admin settings from database"); + JwtSettings jwtLoaded = JacksonUtil.treeToValue(adminJwtSettings.getJsonValue(), JwtSettings.class); + jwtSettings.setRefreshTokenExpTime(jwtLoaded.getRefreshTokenExpTime()); + jwtSettings.setTokenExpirationTime(jwtLoaded.getTokenExpirationTime()); + jwtSettings.setTokenIssuer(jwtLoaded.getTokenIssuer()); + jwtSettings.setTokenSigningKey(jwtLoaded.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(jwtSettings.getTokenSigningKey()); + } + + public void createJwtAdminSettings() { + Objects.requireNonNull(jwtSettings, "JWT settings is null"); + if (!isJwtAdminSettingsExists()) { + if (hasDefaultTokenSigningKey()) { + if (!isAllowedDefaultJwtSigningKey()) { + 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))); + } + } + AdminSettings 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); + } + } + + public boolean isJwtAdminSettingsExists() { + return findJwtAdminSettings() == null; + } + + AdminSettings findJwtAdminSettings() { + return adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, ADMIN_SETTINGS_JWT_KEY); + } + + /* + * Allowing default JWT signing key is not secure + * */ + public boolean isAllowedDefaultJwtSigningKey() { + String allowDefaultJwtSigningKey = System.getenv(TB_ALLOW_DEFAULT_JWT_SIGNING_KEY); + return "true".equalsIgnoreCase(allowDefaultJwtSigningKey); + } + + public void validateJwtTokenSigningKey() { + if (!isJwtAdminSettingsExists()) { + if (hasDefaultTokenSigningKey()) { + if (isAllowedDefaultJwtSigningKey()) { + 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); + } + } + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/install/ConditionValidatorUpgradeServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/install/ConditionValidatorUpgradeServiceImpl.java index f70086abee..27117239f0 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/ConditionValidatorUpgradeServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/install/ConditionValidatorUpgradeServiceImpl.java @@ -19,14 +19,7 @@ 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; +import org.thingsboard.server.config.JwtSettingsService; @Service @Profile("install") @@ -34,30 +27,12 @@ import static org.thingsboard.server.config.JwtSettings.ADMIN_SETTINGS_JWT_KEY; @Slf4j public class ConditionValidatorUpgradeServiceImpl implements ConditionValidatorUpgradeService { - private final AdminSettingsService adminSettingsService; - - private final JwtSettings jwtSettings; + private final JwtSettingsService jwtSettingsService; @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); - } - } - } + jwtSettingsService.validateJwtTokenSigningKey(); } } diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index a113f1e6d1..9c9ffb5188 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -22,7 +22,6 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; @@ -30,7 +29,6 @@ import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Profile; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.Customer; @@ -84,7 +82,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.TenantProfileQueueConfiguration; import org.thingsboard.server.common.data.widget.WidgetsBundle; -import org.thingsboard.server.config.JwtSettings; +import org.thingsboard.server.config.JwtSettingsService; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.device.DeviceCredentialsService; @@ -103,18 +101,13 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService; import javax.annotation.Nullable; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; -import java.nio.charset.StandardCharsets; import java.util.Arrays; -import java.util.Base64; import java.util.Collections; import java.util.List; -import java.util.Objects; import java.util.TreeMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import static org.thingsboard.server.config.JwtSettings.ADMIN_SETTINGS_JWT_KEY; - @Service @Profile("install") @Slf4j @@ -176,7 +169,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { private QueueService queueService; @Autowired - private JwtSettings jwtSettings; + private JwtSettingsService jwtSettingsService; @Bean protected BCryptPasswordEncoder passwordEncoder() { @@ -274,6 +267,11 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, mailSettings); } + @Override + public void createJwtAdminSettings() throws Exception { + jwtSettingsService.createJwtAdminSettings(); + } + @Override public void createOAuth2Templates() throws Exception { installScripts.createOAuth2Templates(); @@ -668,25 +666,4 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { } } - @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); - } - } - }