Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate NGSI-LD Agent to DigitalTwin #487

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion KafkaBridge/config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,15 @@
"adminPassword": "password",
"clientSecretVariable": "MQTT_CLIENT_SECRET",
"authServicePort": 3025,
"tainted": "TAINTED",
"sparkplug": {
"spBKafkaProduce": false,
"spBkafKaTopic": "sparkplugB",
"ngsildKafkaProduce": true,
"ngsildKafkaTopic": "ngsildSpB",
"topics": {
"subscribe": {
"sparkplugb_data_ingestion": "spBv1.0/+/+/+/+"
"sparkplugb_data_ingestion": "$share/kafka/spBv1.0/+/+/+/+"
},
"publish": {
"error": "server/error/{accountId}/{deviceId}",
Expand Down
2 changes: 2 additions & 0 deletions KafkaBridge/lib/authService/acl.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,13 @@ class Acl {
const spBAclKey = spBAccountId + '/' + spBdevId;
const allowed = await this.cache.getValue(spBAclKey, 'acl');
if (allowed === undefined || !(allowed === 'true') || spBdevId !== username) {
this.logger.info('Connection rejected for realm ' + spBAccountId + ' and device ' + spBdevId);
res.sendStatus(400);
} else {
res.status(200).json({ result: 'allow' });
}
} else {
this.logger.warn('Topic sructure not valid.');
res.sendStatus(400);
}
}
Expand Down
28 changes: 16 additions & 12 deletions KafkaBridge/lib/authService/authenticate.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ function getRealm (token) {
}

function validate (token, username) {
const type = token.type;
const did = token.device_id;
if (!type || !did) {
const gateway = token.gateway;
if (did === null || did === undefined || gateway === null || gateway === undefined) {
return false;
}
if (type !== 'device' || did !== username) {
if (did !== username) {
return false;
}
return true;
Expand Down Expand Up @@ -66,7 +66,7 @@ class Authenticate {
return;
} else {
// will also kick out tokens who use the superuser name as deviceId
this.logger.info('Wrong Superuser password.');
this.logger.warn('Wrong Superuser password.');
res.sendStatus(400);
return;
}
Expand All @@ -79,21 +79,25 @@ class Authenticate {
return;
}
if (!validate(decodedToken, username)) {
this.logger.warn('Validation of token failed. Username: ' + username);
res.sendStatus(400);
return;
}
// check whether accounts contains only one element and role is device
const accounts = decodedToken.accounts;
const did = decodedToken.device_id ? decodedToken.device_id : decodedToken.sub;
const accountId = accounts && accounts.length > 0 ? accounts[0].id : null;
const did = decodedToken.device_id;
const gateway = decodedToken.gateway;
const realm = getRealm(decodedToken);
if (did === null || did === undefined || realm === null || realm === undefined) {
this.logger.warn('Validation failed: Device id or realm not valid.');
res.sendStatus(400);
return;
}
if (did === this.config.mqtt.tainted || gateway === this.config.mqtt.tainted) {
this.logger.warn('This token is tained! Rejecting.');
res.sendStatus(400);
}
// put realm/device into the list of accepted topics
await this.cache.setValue(realm + '/' + did, 'acl', 'true');
// put account/device into the list of accepted topics (legacy)
if (accountId) {
const key = accountId + '/' + did;
await this.cache.setValue(key, 'acl', 'true');
}
res.status(200).json({ result: 'allow', is_superuser: 'false' });
}

Expand Down
2 changes: 1 addition & 1 deletion KafkaBridge/mqttBridge/sparkplug_data_ingestion.js
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ module.exports = class SparkplugHandler {
if (Object.values(MESSAGE_TYPE.WITHSEQ).includes(subTopic[2])) {
const validationResult = this.validator.validate(message, dataSchema.SPARKPLUGB);
if (validationResult.errors.length > 0) {
this.logger.warn('Schema rejected message! Message will be discarded: ' + message);
this.logger.warn('Schema rejected message! Message will be discarded: ' + JSON.stringify(message));
} else {
/* Validating SpB seq number if it is alligned with previous or not
* To Do: If seq number is incorrect, send command to device for resend Birth Message
Expand Down
32 changes: 31 additions & 1 deletion KafkaBridge/test/lib_authServiceTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ describe(fileToTest, function () {
class Logger {
info () {}
debug () {}
warn () {}
};
class Cache {
init () {}
};
class Cache {};
ToTest.__set__('Logger', Logger);
ToTest.__set__('Cache', Cache);
it('Shall verify and decode token successfully', function (done) {
Expand All @@ -56,6 +59,32 @@ describe(fileToTest, function () {
done(err);
});
});
it('Shall test initialize', function (done) {
const config = {
keycloak: {
mqttAuthService: {}
},
mqtt: {
clientSecretVariable: 'CLIENTSECRETVARIABLE'
}
};
const Authenticate = ToTest.__get__('Authenticate');
const auth = new Authenticate(config);
class Keycloak {
}
const process = {
env: {
CLIENTSECRETVARIABLE: 'CLIENTSECRETVARIABLE'
}
};
ToTest.__set__('Keycloak', Keycloak);
ToTest.__set__('process', process);
auth.initialize().then(() => {
done();
}).catch(err => {
done(err);
});
});
it('Shall verify and decode token unsuccessfully', function (done) {
const message = 'No valid token';
const config = {};
Expand Down Expand Up @@ -317,6 +346,7 @@ describe(fileToTest, function () {
class Logger {
info () {}
debug () {}
warn () {}
};
class Cache {};
ToTest.__set__('Logger', Logger);
Expand Down
3 changes: 1 addition & 2 deletions KafkaBridge/timescaledb/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ const processMessage = async function ({ topic, partition, message }) {
logger.error('Could not send Datapoints: Neither Property nor Relationship');
return;
}

entityHistoryTable.create(datapoint).then(() => {
entityHistoryTable.upsert(datapoint).then(() => {
logger.debug('Datapoint succefully stored in tsdb table');
})
.catch((err) => logger.error('Error in storing datapoint in tsdb: ' + err));
Expand Down
30 changes: 9 additions & 21 deletions Keycloak/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ARG KEYCLOAK_VERSION="20.0.2"
ARG KEYCLOAK_VERSION="22.0"

FROM node:12-alpine AS keycloak-themes-builder

Expand All @@ -10,38 +10,26 @@ RUN cp node_modules/patternfly/dist/css/patternfly.min.css css/patternfly.min.cs
RUN cp node_modules/patternfly/dist/css/patternfly-additions.min.css css/patternfly-additions.min.css
RUN rm -rf node_modules

FROM maven:3.6.3-jdk-8-slim AS keycloak-modules-builder
FROM alpine:3 AS keycloak-modules-builder

RUN apt -y -qq update && apt -y -qq install build-essential
RUN apk add zip

RUN mkdir /deployments

ADD oisp-event-listener /modules/oisp-event-listener
ADD oisp-js-policies /modules/oisp-js-policies
ADD iff-js-providers /modules/iff-js-providers

WORKDIR /modules/oisp-event-listener
RUN mvn checkstyle:check pmd:check clean package
RUN cp /modules/oisp-event-listener/target/oisp-event-listener.jar /deployments/oisp-event-listener.jar

WORKDIR /modules/oisp-js-policies
RUN mvn clean package
RUN cp ./target/oisp-js-policies.jar ./target/nashorn-core-15.3.jar ./target/asm-7.3.1.jar ./target/asm-util-7.3.1.jar ./target/asm-commons-7.3.1.jar ./target/httpclient-4.5.11.jar /deployments
WORKDIR /modules/iff-js-providers
RUN zip -r iff-js-providers.jar *
RUN cp ./iff-js-providers.jar /deployments

COPY --from=keycloak-themes-builder --chown=1000 /themes /themes
WORKDIR /themes
RUN jar cf fusion.jar theme META-INF && cp fusion.jar /deployments/fusion.jar
RUN zip -r fusion.jar theme META-INF && cp fusion.jar /deployments/fusion.jar

FROM quay.io/keycloak/keycloak:${KEYCLOAK_VERSION}

COPY --from=keycloak-modules-builder --chown=1000 /deployments/fusion.jar /opt/keycloak/providers/fusion.jar

COPY --from=keycloak-modules-builder --chown=1000 /deployments/oisp-event-listener.jar /opt/keycloak/providers/oisp-event-listener.jar

COPY --from=keycloak-modules-builder --chown=1000 /deployments/nashorn-core-15.3.jar /opt/keycloak/providers/nashorn-core-15.3.jar
COPY --from=keycloak-modules-builder --chown=1000 /deployments/asm-7.3.1.jar /opt/keycloak/providers/asm-7.3.1.jar
COPY --from=keycloak-modules-builder --chown=1000 /deployments/asm-util-7.3.1.jar /opt/keycloak/providers/asm-util-7.3.1.jar
COPY --from=keycloak-modules-builder --chown=1000 /deployments/asm-commons-7.3.1.jar /opt/keycloak/providers/asm-commons-7.3.1.jar
COPY --from=keycloak-modules-builder --chown=1000 /deployments/httpclient-4.5.11.jar /opt/keycloak/providers/httpclient-4.5.11.jar
COPY --from=keycloak-modules-builder --chown=1000 /deployments/oisp-js-policies.jar /opt/keycloak/providers/oisp-js-policies.jar
COPY --from=keycloak-modules-builder --chown=1000 /deployments/iff-js-providers.jar /opt/keycloak/providers/iff-js-providers.jar

RUN /opt/keycloak/bin/kc.sh build --db=postgres
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,6 @@
"authenticators": [],
"policies": [],
"mappers": [
{
"name": "Accounts Mapper",
"fileName": "accounts-mapper.js",
"description": "accounts that user has acces"
},
{
"name": "Access Type Mapper",
"fileName": "type-mapper.js",
"description": "token type - user or device"
},
{
"name": "Gateway ID Mapper",
"fileName": "gateway-mapper.js",
Expand Down
76 changes: 76 additions & 0 deletions Keycloak/iff-js-providers/deviceid-mapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* Copyright (c) 2023 Intel Corporation
*
* 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.
*/

/**
* Available variables:
* user - the current user
* realm - the current realm
* token - the current token
* userSession - the current userSession
* keycloakSession - the current keycloakSession
*/

var onboarding_token_expiration = java.lang.System.getenv("OISP_FRONTEND_DEVICE_ACCOUNT_ENDPOINT");
var tainted = 'TAINTED';
exports = tainted;
var deviceIdH = keycloakSession.getContext().getRequestHeaders()
.getRequestHeader("X-DeviceID")[0];
var inputRequest = keycloakSession.getContext().getHttpRequest();
var params = inputRequest.getDecodedFormParameters();
var origTokenParam = params.getFirst("orig_token");
var grantType = params.getFirst("grant_type");
var tokens = keycloakSession.tokens();
var origToken = tokens.decode(origTokenParam, Java.type("org.keycloak.representations.AccessToken").class)

if (typeof(onboarding_token_expiration) !== 'number') {
// if not otherwise configured onboardig token is valid for 5 minutes
onboarding_token_expiration = 300;
}
if (grantType === 'refresh_token' && origToken !== null) {
var session = userSession.getId();
var otherClaims = origToken.getOtherClaims();
var origTokenDeviceId;
if (otherClaims !== null) {

origTokenDeviceId = otherClaims.get("device_id");
}
var origTokenSession = origToken.getSessionId();

if (origTokenDeviceId !== null && origTokenDeviceId !== undefined) {
// Has origToken same session?
if (origTokenSession !== session) {
print("Warning: Rejecting token due to session mismatch between refresh_token and orig_token")
exports = tainted;
} else {
exports = origTokenDeviceId;
}
} else {
// If there is no origTokenDeviceId, there must be an X-DeviceId header AND origToken must be valid
if (!origToken.isExpired() && deviceIdH !== null && deviceIdH !== undefined) {
exports = deviceIdH
} else {
print("Warning: Rejecting token due to orig_token is expired or there is not valid X-DeviceId Header.")
exports = tainted;
}
}
} else if (grantType === 'password'){
var currentTimeInSeconds = new Date().getTime() / 1000;
token.exp(currentTimeInSeconds + onboarding_token_expiration);
exports = null
} else if (origToken === null) {
print("Warning: Rejecting token due to invalid orig_token.")
exports = tainted
}
69 changes: 69 additions & 0 deletions Keycloak/iff-js-providers/gateway-mapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* Copyright (c) 2023 Intel Corporation
*
* 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.
*/

/**
* Available variables:
* user - the current user
* realm - the current realm
* token - the current token
* userSession - the current userSession
* keycloakSession - the current keycloakSession
*/

var tainted = 'TAINTED';
exports = tainted;
var gatewayIdH = keycloakSession.getContext().getRequestHeaders()
.getRequestHeader("X-GatewayID")[0];
var inputRequest = keycloakSession.getContext().getHttpRequest();
var params = inputRequest.getDecodedFormParameters();
var origTokenParam = params.getFirst("orig_token");
var grantType = params.getFirst("grant_type");
var tokens = keycloakSession.tokens();
var origToken = tokens.decode(origTokenParam, Java.type("org.keycloak.representations.AccessToken").class)

if (grantType === 'refresh_token' && origToken !== null) {
var session = userSession.getId();
var origToken = tokens.decode(origTokenParam, Java.type("org.keycloak.representations.AccessToken").class)
var otherClaims = origToken.getOtherClaims();
var origTokenGatewayId;
if (otherClaims !== null) {
origTokenGatewayId = otherClaims.get("gateway");
}
var origTokenSession = origToken.getSessionId();

if (origTokenGatewayId !== null && origTokenGatewayId !== undefined) {
// Has origToken same session?
if (origTokenSession !== session) {
print("Warning: Rejecting token due to session mismatch between refresh_token and orig_token")
exports = tainted;
} else {
exports = origTokenGatewayId;
}
} else {
// If there is no origTokenGatewayId, there must be an X-GatewayId header AND origToken must be valid
if (!origToken.isExpired() && gatewayIdH !== null && gatewayIdH !== undefined) {
exports = gatewayIdH
} else {
print("Warning: Rejecting token due to orig_token is expired or there is not valid X-GatewayId Header.")
exports = tainted;
}
}
} else if (grantType === 'password'){
exports = null
} else if (origToken === null) {
print("Warning: Rejecting token due to invalid orig_token.")
exports = tainted
}
Loading
Loading