51 changed files with 1036 additions and 258 deletions
@ -0,0 +1,22 @@ |
|||
-----BEGIN CERTIFICATE----- |
|||
MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ |
|||
RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD |
|||
VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX |
|||
DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y |
|||
ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy |
|||
VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr |
|||
mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr |
|||
IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK |
|||
mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu |
|||
XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy |
|||
dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye |
|||
jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 |
|||
BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 |
|||
DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 |
|||
9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx |
|||
jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 |
|||
Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz |
|||
ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS |
|||
R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp |
|||
-----END CERTIFICATE----- |
|||
|
|||
@ -1,48 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.stats; |
|||
|
|||
import io.micrometer.core.instrument.Counter; |
|||
import io.micrometer.core.instrument.MeterRegistry; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.service.metrics.StubCounter; |
|||
|
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
|
|||
@Service |
|||
public class StatsCounterFactory { |
|||
private static final String STATS_NAME_TAG = "statsName"; |
|||
|
|||
private static final Counter STUB_COUNTER = new StubCounter(); |
|||
|
|||
@Autowired |
|||
private MeterRegistry meterRegistry; |
|||
|
|||
@Value("${metrics.enabled}") |
|||
private Boolean metricsEnabled; |
|||
|
|||
public StatsCounter createStatsCounter(String key, String statsName) { |
|||
return new StatsCounter( |
|||
new AtomicInteger(0), |
|||
metricsEnabled ? |
|||
meterRegistry.counter(key, STATS_NAME_TAG, statsName) |
|||
: STUB_COUNTER, |
|||
statsName |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,92 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!-- |
|||
|
|||
Copyright © 2016-2020 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. |
|||
|
|||
--> |
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" |
|||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
|||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
|||
<modelVersion>4.0.0</modelVersion> |
|||
<parent> |
|||
<groupId>org.thingsboard</groupId> |
|||
<version>3.1.0-SNAPSHOT</version> |
|||
<artifactId>common</artifactId> |
|||
</parent> |
|||
<groupId>org.thingsboard.common</groupId> |
|||
<artifactId>stats</artifactId> |
|||
<packaging>jar</packaging> |
|||
|
|||
<name>Thingsboard Server Stats</name> |
|||
<url>https://thingsboard.io</url> |
|||
|
|||
<properties> |
|||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
|||
<main.dir>${basedir}/../..</main.dir> |
|||
</properties> |
|||
|
|||
<dependencies> |
|||
<dependency> |
|||
<groupId>com.google.guava</groupId> |
|||
<artifactId>guava</artifactId> |
|||
<scope>provided</scope> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.slf4j</groupId> |
|||
<artifactId>slf4j-api</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.slf4j</groupId> |
|||
<artifactId>log4j-over-slf4j</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>ch.qos.logback</groupId> |
|||
<artifactId>logback-core</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>ch.qos.logback</groupId> |
|||
<artifactId>logback-classic</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-starter-actuator</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>io.micrometer</groupId> |
|||
<artifactId>micrometer-core</artifactId> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>io.micrometer</groupId> |
|||
<artifactId>micrometer-registry-prometheus</artifactId> |
|||
</dependency> |
|||
|
|||
<dependency> |
|||
<groupId>junit</groupId> |
|||
<artifactId>junit</artifactId> |
|||
<scope>test</scope> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.mockito</groupId> |
|||
<artifactId>mockito-all</artifactId> |
|||
<scope>test</scope> |
|||
</dependency> |
|||
</dependencies> |
|||
|
|||
<build> |
|||
<plugins> |
|||
</plugins> |
|||
</build> |
|||
|
|||
</project> |
|||
@ -0,0 +1,91 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.stats; |
|||
|
|||
import io.micrometer.core.instrument.Counter; |
|||
import io.micrometer.core.instrument.MeterRegistry; |
|||
import io.micrometer.core.instrument.Tags; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
|
|||
@Service |
|||
public class DefaultStatsFactory implements StatsFactory { |
|||
private static final String TOTAL_MSGS = "totalMsgs"; |
|||
private static final String SUCCESSFUL_MSGS = "successfulMsgs"; |
|||
private static final String FAILED_MSGS = "failedMsgs"; |
|||
|
|||
private static final String STATS_NAME_TAG = "statsName"; |
|||
|
|||
private static final Counter STUB_COUNTER = new StubCounter(); |
|||
|
|||
@Autowired |
|||
private MeterRegistry meterRegistry; |
|||
|
|||
@Value("${metrics.enabled:false}") |
|||
private Boolean metricsEnabled; |
|||
|
|||
@Override |
|||
public StatsCounter createStatsCounter(String key, String statsName) { |
|||
return new StatsCounter( |
|||
new AtomicInteger(0), |
|||
metricsEnabled ? |
|||
meterRegistry.counter(key, STATS_NAME_TAG, statsName) |
|||
: STUB_COUNTER, |
|||
statsName |
|||
); |
|||
} |
|||
|
|||
@Override |
|||
public DefaultCounter createDefaultCounter(String key, String... tags) { |
|||
return new DefaultCounter( |
|||
new AtomicInteger(0), |
|||
metricsEnabled ? |
|||
meterRegistry.counter(key, tags) |
|||
: STUB_COUNTER |
|||
); |
|||
} |
|||
|
|||
@Override |
|||
public <T extends Number> T createGauge(String key, T number, String... tags) { |
|||
return meterRegistry.gauge(key, Tags.of(tags), number); |
|||
} |
|||
|
|||
@Override |
|||
public MessagesStats createMessagesStats(String key) { |
|||
StatsCounter totalCounter = createStatsCounter(key, TOTAL_MSGS); |
|||
StatsCounter successfulCounter = createStatsCounter(key, SUCCESSFUL_MSGS); |
|||
StatsCounter failedCounter = createStatsCounter(key, FAILED_MSGS); |
|||
return new DefaultMessagesStats(totalCounter, successfulCounter, failedCounter); |
|||
} |
|||
|
|||
private static class StubCounter implements Counter { |
|||
@Override |
|||
public void increment(double amount) {} |
|||
|
|||
@Override |
|||
public double count() { |
|||
return 0; |
|||
} |
|||
|
|||
@Override |
|||
public Id getId() { |
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.stats; |
|||
|
|||
import io.micrometer.core.instrument.Counter; |
|||
|
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
|
|||
public class StatsCounter extends DefaultCounter { |
|||
private final String name; |
|||
|
|||
public StatsCounter(AtomicInteger aiCounter, Counter micrometerCounter, String name) { |
|||
super(aiCounter, micrometerCounter); |
|||
this.name = name; |
|||
} |
|||
|
|||
public String getName() { |
|||
return name; |
|||
} |
|||
} |
|||
@ -0,0 +1,99 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.common.util; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
|
|||
import javax.crypto.Mac; |
|||
import javax.crypto.spec.SecretKeySpec; |
|||
import java.io.IOException; |
|||
import java.net.URLEncoder; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.nio.file.Files; |
|||
import java.nio.file.Path; |
|||
import java.nio.file.Paths; |
|||
import java.util.Base64; |
|||
|
|||
@Slf4j |
|||
public final class AzureIotHubUtil { |
|||
private static final String BASE_DIR_PATH = System.getProperty("user.dir"); |
|||
private static final String APP_DIR = "application"; |
|||
private static final String SRC_DIR = "src"; |
|||
private static final String MAIN_DIR = "main"; |
|||
private static final String DATA_DIR = "data"; |
|||
private static final String CERTS_DIR = "certs"; |
|||
private static final String AZURE_DIR = "azure"; |
|||
private static final String FILE_NAME = "BaltimoreCyberTrustRoot.crt.pem"; |
|||
|
|||
private static final Path FULL_FILE_PATH; |
|||
|
|||
static { |
|||
if (BASE_DIR_PATH.endsWith("bin")) { |
|||
FULL_FILE_PATH = Paths.get(BASE_DIR_PATH.replaceAll("bin$", ""), DATA_DIR, CERTS_DIR, AZURE_DIR, FILE_NAME); |
|||
} else if (BASE_DIR_PATH.endsWith("conf")) { |
|||
FULL_FILE_PATH = Paths.get(BASE_DIR_PATH.replaceAll("conf$", ""), DATA_DIR, CERTS_DIR, AZURE_DIR, FILE_NAME); |
|||
} else { |
|||
FULL_FILE_PATH = Paths.get(BASE_DIR_PATH, APP_DIR, SRC_DIR, MAIN_DIR, DATA_DIR, CERTS_DIR, AZURE_DIR, FILE_NAME); |
|||
} |
|||
} |
|||
|
|||
private static final long SAS_TOKEN_VALID_SECS = 365 * 24 * 60 * 60; |
|||
private static final long ONE_SECOND_IN_MILLISECONDS = 1000; |
|||
|
|||
private static final String SAS_TOKEN_FORMAT = "SharedAccessSignature sr=%s&sig=%s&se=%s"; |
|||
|
|||
private static final String USERNAME_FORMAT = "%s/%s/?api-version=2018-06-30"; |
|||
|
|||
private AzureIotHubUtil() { |
|||
} |
|||
|
|||
public static String buildUsername(String host, String deviceId) { |
|||
return String.format(USERNAME_FORMAT, host, deviceId); |
|||
} |
|||
|
|||
public static String buildSasToken(String host, String sasKey) { |
|||
try { |
|||
final String targetUri = URLEncoder.encode(host.toLowerCase(), "UTF-8"); |
|||
final long expiryTime = buildExpiresOn(); |
|||
String toSign = targetUri + "\n" + expiryTime; |
|||
byte[] keyBytes = Base64.getDecoder().decode(sasKey.getBytes(StandardCharsets.UTF_8)); |
|||
SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA256"); |
|||
Mac mac = Mac.getInstance("HmacSHA256"); |
|||
mac.init(signingKey); |
|||
byte[] rawHmac = mac.doFinal(toSign.getBytes(StandardCharsets.UTF_8)); |
|||
String signature = URLEncoder.encode(Base64.getEncoder().encodeToString(rawHmac), "UTF-8"); |
|||
return String.format(SAS_TOKEN_FORMAT, targetUri, signature, expiryTime); |
|||
} catch (Exception e) { |
|||
throw new RuntimeException("Failed to build SAS token!!!", e); |
|||
} |
|||
} |
|||
|
|||
private static long buildExpiresOn() { |
|||
long expiresOnDate = System.currentTimeMillis(); |
|||
expiresOnDate += SAS_TOKEN_VALID_SECS * ONE_SECOND_IN_MILLISECONDS; |
|||
return expiresOnDate / ONE_SECOND_IN_MILLISECONDS; |
|||
} |
|||
|
|||
public static String getDefaultCaCert() { |
|||
try { |
|||
return new String(Files.readAllBytes(FULL_FILE_PATH)); |
|||
} catch (IOException e) { |
|||
log.error("Failed to load Default CaCert file!!! [{}]", FULL_FILE_PATH.toString()); |
|||
throw new RuntimeException("Failed to load Default CaCert file!!!"); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,90 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.dao.util; |
|||
|
|||
import lombok.Getter; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.stats.DefaultCounter; |
|||
import org.thingsboard.server.common.stats.StatsCounter; |
|||
import org.thingsboard.server.common.stats.StatsFactory; |
|||
import org.thingsboard.server.common.stats.StatsType; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
|
|||
@Slf4j |
|||
@Getter |
|||
public class BufferedRateExecutorStats { |
|||
private static final String TENANT_ID_TAG = "tenantId"; |
|||
|
|||
|
|||
private static final String TOTAL_ADDED = "totalAdded"; |
|||
private static final String TOTAL_LAUNCHED = "totalLaunched"; |
|||
private static final String TOTAL_RELEASED = "totalReleased"; |
|||
private static final String TOTAL_FAILED = "totalFailed"; |
|||
private static final String TOTAL_EXPIRED = "totalExpired"; |
|||
private static final String TOTAL_REJECTED = "totalRejected"; |
|||
private static final String TOTAL_RATE_LIMITED = "totalRateLimited"; |
|||
|
|||
private final StatsFactory statsFactory; |
|||
|
|||
private final ConcurrentMap<TenantId, DefaultCounter> rateLimitedTenants = new ConcurrentHashMap<>(); |
|||
|
|||
private final List<StatsCounter> statsCounters = new ArrayList<>(); |
|||
|
|||
private final StatsCounter totalAdded; |
|||
private final StatsCounter totalLaunched; |
|||
private final StatsCounter totalReleased; |
|||
private final StatsCounter totalFailed; |
|||
private final StatsCounter totalExpired; |
|||
private final StatsCounter totalRejected; |
|||
private final StatsCounter totalRateLimited; |
|||
|
|||
public BufferedRateExecutorStats(StatsFactory statsFactory) { |
|||
this.statsFactory = statsFactory; |
|||
|
|||
String key = StatsType.RATE_EXECUTOR.getName(); |
|||
|
|||
this.totalAdded = statsFactory.createStatsCounter(key, TOTAL_ADDED); |
|||
this.totalLaunched = statsFactory.createStatsCounter(key, TOTAL_LAUNCHED); |
|||
this.totalReleased = statsFactory.createStatsCounter(key, TOTAL_RELEASED); |
|||
this.totalFailed = statsFactory.createStatsCounter(key, TOTAL_FAILED); |
|||
this.totalExpired = statsFactory.createStatsCounter(key, TOTAL_EXPIRED); |
|||
this.totalRejected = statsFactory.createStatsCounter(key, TOTAL_REJECTED); |
|||
this.totalRateLimited = statsFactory.createStatsCounter(key, TOTAL_RATE_LIMITED); |
|||
|
|||
this.statsCounters.add(totalAdded); |
|||
this.statsCounters.add(totalLaunched); |
|||
this.statsCounters.add(totalReleased); |
|||
this.statsCounters.add(totalFailed); |
|||
this.statsCounters.add(totalExpired); |
|||
this.statsCounters.add(totalRejected); |
|||
this.statsCounters.add(totalRateLimited); |
|||
} |
|||
|
|||
public void incrementRateLimitedTenant(TenantId tenantId){ |
|||
rateLimitedTenants.computeIfAbsent(tenantId, |
|||
tId -> { |
|||
String key = StatsType.RATE_EXECUTOR.getName() + ".tenant"; |
|||
return statsFactory.createDefaultCounter(key, TENANT_ID_TAG, tId.toString()); |
|||
} |
|||
) |
|||
.increment(); |
|||
} |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.rule.engine.mqtt.azure; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import io.netty.handler.ssl.ClientAuth; |
|||
import io.netty.handler.ssl.SslContext; |
|||
import io.netty.handler.ssl.SslContextBuilder; |
|||
import lombok.Data; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.codec.binary.Base64; |
|||
import org.bouncycastle.jce.provider.BouncyCastleProvider; |
|||
import org.thingsboard.common.util.AzureIotHubUtil; |
|||
import org.thingsboard.mqtt.MqttClientConfig; |
|||
import org.thingsboard.rule.engine.mqtt.credentials.MqttClientCredentials; |
|||
|
|||
import javax.net.ssl.TrustManagerFactory; |
|||
import java.io.ByteArrayInputStream; |
|||
import java.security.KeyStore; |
|||
import java.security.Security; |
|||
import java.security.cert.CertificateFactory; |
|||
import java.security.cert.X509Certificate; |
|||
import java.util.Optional; |
|||
|
|||
@Data |
|||
@Slf4j |
|||
@JsonIgnoreProperties(ignoreUnknown = true) |
|||
public class AzureIotHubSasCredentials implements MqttClientCredentials { |
|||
private String sasKey; |
|||
private String caCert; |
|||
|
|||
@Override |
|||
public Optional<SslContext> initSslContext() { |
|||
try { |
|||
Security.addProvider(new BouncyCastleProvider()); |
|||
if (caCert == null || caCert.isEmpty()) { |
|||
caCert = AzureIotHubUtil.getDefaultCaCert(); |
|||
} |
|||
return Optional.of(SslContextBuilder.forClient() |
|||
.trustManager(createAndInitTrustManagerFactory()) |
|||
.clientAuth(ClientAuth.REQUIRE) |
|||
.build()); |
|||
} catch (Exception e) { |
|||
log.error("[{}] Creating TLS factory failed!", caCert, e); |
|||
throw new RuntimeException("Creating TLS factory failed!", e); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void configure(MqttClientConfig config) { |
|||
} |
|||
|
|||
private TrustManagerFactory createAndInitTrustManagerFactory() throws Exception { |
|||
X509Certificate caCertHolder; |
|||
caCertHolder = readCertFile(caCert); |
|||
|
|||
KeyStore caKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); |
|||
caKeyStore.load(null, null); |
|||
caKeyStore.setCertificateEntry("caCert-cert", caCertHolder); |
|||
|
|||
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); |
|||
trustManagerFactory.init(caKeyStore); |
|||
return trustManagerFactory; |
|||
} |
|||
|
|||
private X509Certificate readCertFile(String fileContent) throws Exception { |
|||
X509Certificate certificate = null; |
|||
if (fileContent != null && !fileContent.trim().isEmpty()) { |
|||
fileContent = fileContent.replace("-----BEGIN CERTIFICATE-----", "") |
|||
.replace("-----END CERTIFICATE-----", "") |
|||
.replaceAll("\\s", ""); |
|||
byte[] decoded = Base64.decodeBase64(fileContent); |
|||
CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); |
|||
certificate = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(decoded)); |
|||
} |
|||
return certificate; |
|||
} |
|||
} |
|||
@ -0,0 +1,86 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.rule.engine.mqtt.azure; |
|||
|
|||
import io.netty.handler.codec.mqtt.MqttVersion; |
|||
import io.netty.handler.ssl.SslContext; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.common.util.AzureIotHubUtil; |
|||
import org.thingsboard.mqtt.MqttClientConfig; |
|||
import org.thingsboard.rule.engine.api.RuleNode; |
|||
import org.thingsboard.rule.engine.api.TbContext; |
|||
import org.thingsboard.rule.engine.api.TbNodeConfiguration; |
|||
import org.thingsboard.rule.engine.api.TbNodeException; |
|||
import org.thingsboard.rule.engine.api.util.TbNodeUtils; |
|||
import org.thingsboard.rule.engine.mqtt.TbMqttNode; |
|||
import org.thingsboard.rule.engine.mqtt.TbMqttNodeConfiguration; |
|||
import org.thingsboard.rule.engine.mqtt.credentials.CertPemClientCredentials; |
|||
import org.thingsboard.rule.engine.mqtt.credentials.MqttClientCredentials; |
|||
import org.thingsboard.server.common.data.plugin.ComponentType; |
|||
|
|||
import java.util.Optional; |
|||
|
|||
@Slf4j |
|||
@RuleNode( |
|||
type = ComponentType.EXTERNAL, |
|||
name = "azure iot hub", |
|||
configClazz = TbAzureIotHubNodeConfiguration.class, |
|||
nodeDescription = "Publish messages to the Azure IoT Hub", |
|||
nodeDetails = "Will publish message payload to the Azure IoT Hub with QoS <b>AT_LEAST_ONCE</b>.", |
|||
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, |
|||
configDirective = "tbActionNodeAzureIotHubConfig" |
|||
) |
|||
public class TbAzureIotHubNode extends TbMqttNode { |
|||
@Override |
|||
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { |
|||
try { |
|||
this.mqttNodeConfiguration = TbNodeUtils.convert(configuration, TbMqttNodeConfiguration.class); |
|||
mqttNodeConfiguration.setPort(8883); |
|||
mqttNodeConfiguration.setCleanSession(true); |
|||
MqttClientCredentials credentials = mqttNodeConfiguration.getCredentials(); |
|||
mqttNodeConfiguration.setCredentials(new MqttClientCredentials() { |
|||
@Override |
|||
public Optional<SslContext> initSslContext() { |
|||
if (credentials instanceof AzureIotHubSasCredentials) { |
|||
AzureIotHubSasCredentials sasCredentials = (AzureIotHubSasCredentials) credentials; |
|||
if (sasCredentials.getCaCert() == null || sasCredentials.getCaCert().isEmpty()) { |
|||
sasCredentials.setCaCert(AzureIotHubUtil.getDefaultCaCert()); |
|||
} |
|||
} else if (credentials instanceof CertPemClientCredentials) { |
|||
CertPemClientCredentials pemCredentials = (CertPemClientCredentials) credentials; |
|||
if (pemCredentials.getCaCert() == null || pemCredentials.getCaCert().isEmpty()) { |
|||
pemCredentials.setCaCert(AzureIotHubUtil.getDefaultCaCert()); |
|||
} |
|||
} |
|||
return credentials.initSslContext(); |
|||
} |
|||
|
|||
@Override |
|||
public void configure(MqttClientConfig config) { |
|||
config.setProtocolVersion(MqttVersion.MQTT_3_1_1); |
|||
config.setUsername(AzureIotHubUtil.buildUsername(mqttNodeConfiguration.getHost(), config.getClientId())); |
|||
if (credentials instanceof AzureIotHubSasCredentials) { |
|||
AzureIotHubSasCredentials sasCredentials = (AzureIotHubSasCredentials) credentials; |
|||
config.setPassword(AzureIotHubUtil.buildSasToken(mqttNodeConfiguration.getHost(), sasCredentials.getSasKey())); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
this.mqttClient = initClient(ctx); |
|||
} catch (Exception e) { |
|||
throw new TbNodeException(e); |
|||
} } |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.rule.engine.mqtt.azure; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.rule.engine.api.NodeConfiguration; |
|||
import org.thingsboard.rule.engine.mqtt.TbMqttNodeConfiguration; |
|||
import org.thingsboard.rule.engine.mqtt.credentials.AnonymousCredentials; |
|||
import org.thingsboard.rule.engine.mqtt.credentials.MqttClientCredentials; |
|||
|
|||
@Data |
|||
public class TbAzureIotHubNodeConfiguration extends TbMqttNodeConfiguration { |
|||
|
|||
@Override |
|||
public TbAzureIotHubNodeConfiguration defaultConfiguration() { |
|||
TbAzureIotHubNodeConfiguration configuration = new TbAzureIotHubNodeConfiguration(); |
|||
configuration.setTopicPattern("devices/<device_id>/messages/events/"); |
|||
configuration.setHost("<iot-hub-name>.azure-devices.net"); |
|||
configuration.setPort(8883); |
|||
configuration.setConnectTimeoutSec(10); |
|||
configuration.setCleanSession(true); |
|||
configuration.setSsl(true); |
|||
configuration.setCredentials(new AzureIotHubSasCredentials()); |
|||
return configuration; |
|||
} |
|||
|
|||
} |
|||
Loading…
Reference in new issue