Introduction
Recently a question was raised here in the community how the secure store functionality can be used in a XS Advanced application without using the $.security.Store API available via the XSJS compatibility layer. Until HANA 2.0 SPS02 there was no official information available although the functionality was already part of the system, but not officially released, at least to my knowledge. With HANA 2.0 SPS03 in the SAP HANA Developer Guide for XS Advanced the Application Security chapter was enhanced by a new sub-chapter Maintain Values in the SAP HANA Secure Store which adds the information that the secure store can be used by new procedures:
◈ SYS.USER_SECURESTORE_INSERT to insert new entries
◈ SYS.USER_SECURESTORE_RETRIEVE to get a secure store entry
◈ SYS.USER_SECURESTORE_DELETE to delete a secure store entry
As the documentation at this point did not give me – let me say – enough information (e.g. parameters and their meanings) and the examples are “just” for Java (also w/o further describing any details) I experimented a little bit with the “new” procedures within a XS Advanced NodeJS module on a SAP HANA Express Edition installation (based on HANA 2.0 SPS03).
Finding the interface information for the Secure Store procedures
As we know from the documentation we somehow have to call procedures to insert/retrieve/delete secure store values. So it would be good to know the interface of the procedures. At least somehow in the current HANA 2.0 SPS03 documentation that information is missing, because it is not really important I think (you recognize the irony). So where to find that information. Good that there exists a system view PROCEDURES which shows at least the technical details. Lets switch to the Database Explorer, connect to SystemDB and do the following query:
SYS.USER_SECURESTORE_INSERT
Parameter | Direction | Type | Description |
STORE_NAME | IN | NVARCHAR(530) | The store name of the secure store. |
FOR_XS_APPLICATIONUSER | IN | BOOLEAN | A boolean to indicate if the value is stored for the application user instead of the technical user. |
KEY | IN | NVARCHAR(1024) | Key within the secure store. |
VALUE | IN | VARBINARY(5000) | Value of the secure story entry in binary. |
SYS.USER_SECURESTORE_RETRIEVE
Parameter | Direction |
STORE_NAME | IN |
FOR_XS_APPLICATION_USER | IN |
KEY | IN |
VALUE | Out |
SYS.USER_SECURESTORE_DELETE
Parameter | Direction |
STORE_NAME | IN |
FOR_XS_APPLICATION_USER | IN |
KEY | IN |
Using the Secure Store Procedures within a NodeJS module
As we now know some more details about the procedures we are going to use them within a NodeJS module of an XS Advanced Multi-Target application. Please consider that the following coding contains just quick and dirty examples, so code structuring/encapsulation/error handling/… were not in focus. In a real-world application you have to consider that, but of course you know that.
Create a HANA service instance with “securestore” plan
To be able to use the secure store an instance of the “hana” (or “managed-hana”) service has to be created with service plan “securestore”.
With the new XS Advanced Cockpit this can be done in a very easy way by going to the “hana” service -> Instances -> New Instance. I created one with the name “securestore_test-hana”. This service instance will be bound later to our NodeJS application.
If you wanna create the service via the XS command line tools you can do it like following (make sure to be in the right org and space):
xs cs hana securestore securestore_test-hana
Create a NodeJS module
Next step is to create a Multi-Target Application within the SAP Web IDE for SAP HANA using the project from template functionality. Within the created MTA a new node module is added (I called it node_securestore_test). To this node module a simple express application will be added with routes to interact with the secure store.
Prepare the development descriptor file (mta.yaml)
The resource “securestore_test-db” pointing to the before created hana securestore service needs to be added to the development descriptor file as resource. This resource is required by the NodeJS module.
For the test also an xsuaa service instance “securestore_test-uaa” was created, added as resource and added as required resource for the NodeJS module.
ID: xsa_securestore_test
_schema-version: '2.0'
version: 0.0.1
modules:
- name: node_securestore_test
type: nodejs
path: node_securestore_test
provides:
- name: node_securestore_test_api
properties:
url: ${default-url}
requires:
- name: securestore_test-uaa
- name: securestore_test-db
resources:
- name: securestore_test-uaa
type: com.sap.xs.uaa-space
parameters:
config_path: ./xs-security.json
- name: securestore_test-db
type: com.sap.xs.hana-securestore
parameters:
service-name: securestore_test-hana
Prepare the server.js file
Within the server.js file (created by the NodeJS module creation) an express app is created, the hana securestore instance is added as middleware to the express app and an HTTP server is started. To the express app the routes for the secure store tests, defined in the router folder, are added too (details are described in the next chapter).
"use strict";
// create express app
var app = require("express")();
// add secure store middleware to express app
var xsenv = require("@sap/xsenv");
var hdbext = require("@sap/hdbext");
var hanaOptions = xsenv.getServices({
secureStore: {
name: "securestore_test-hana"
}
});
app.use(
hdbext.middleware(hanaOptions.secureStore)
);
// create server instance
var server = require("http").createServer();
// setup routes of express app
var router = require("./router")(app, server);
// start server
var port = process.env.PORT || 3000;
server.on("request", app);
server.listen(port, function() {
console.info('HTTP Server: ${server.address().port}');
});
Prepare the express routes for inserting/retrieving/deleting a secure store entry
In this step the express routes and the endpoint logic to interact with the secure store is described.
First a folder “router” is created. In this folder a file “index.js” is created with the following content which describes the available routes. Within the “server.js” file (see above) that file is used by the “setup of routes” step.
The following routes are made available by the router implementation:
◈ /createSecureStoreEntry to create a secure store entry
◈ /retrieveSecureStoreEntry to retrieve the created secure store entry
◈ /deleteSecureStoreEntry to delete the created secure store entry
"use strict";
module.exports = (app, server) => {
app.use("/createSecureStoreEntry", require("./routes/createSecureStoreEntry")());
app.use("/retrieveSecureStoreEntry", require("./routes/retrieveSecureStoreEntry")());
app.use("/deleteSecureStoreEntry", require("./routes/deleteSecureStoreEntry")());
};
As it can be seen in the above router coding the route implementations are created in a new folder “routes” (created within the “router”) folder.
Creating a secure store entry
Within file “createSecureStoreEntry.js” with the module “@sap/hdbext” the procedure “SYS”.”USER_SECURESTORE_INSERT” is loaded and executed. As store name “TEST_STORE” is used, the used key is “TEST_VALUE” and the value itself is a dummy string converted to a binary.
"use strict";
module.exports = function() {
var express = require("express");
var hdbext = require("@sap/hdbext");
var app = express.Router();
app.get('/', function(req, res) {
hdbext.loadProcedure(req.db, "SYS", "USER_SECURESTORE_INSERT", function(error, proc) {
if(error) {
res.send("Error during procedure loading:" + error.message);
return;
}
proc({"STORE_NAME":"TEST_STORE",
"FOR_XS_APPLICATIONUSER": false,
"KEY": "TEST_VALUE",
"VALUE": Buffer.from("Test Secure Store Value")}, function(error){
if(error) {
res.send("Error during procedure execution: " + error.message);
return;
}
res.send("Entry in secure store successfully created.");
});
});
});
return app;
};
Retrieving a secure store entry
For retrieving the created value in store “TEST_STORE” with key “TEST_VALUE” procedure “SYS”.”USER_SECURESTORE_RETRIEVE” is used. The value store as binary is converted back to a string value for the output.
"use strict";
module.exports = function() {
var express = require("express");
var hdbext = require("@sap/hdbext");
var app = express.Router();
app.get('/', function(req, res) {
hdbext.loadProcedure(req.db, "SYS", "USER_SECURESTORE_RETRIEVE", function(error, proc) {
if(error) {
res.send("Error during procedure loading:" + error.message);
return;
}
proc({"STORE_NAME":"TEST_STORE",
"FOR_XS_APPLICATIONUSER": false,
"KEY": "TEST_VALUE"}, function(error, out_parameters){
if(error) {
res.send("Error during procedure execution: " + error.message);
return;
}
if(!out_parameters.hasOwnProperty("VALUE")) {
res.send("Value of secure store entry could not be determined.");
return;
}
res.send("Retrieved value: " + Buffer.from(out_parameters["VALUE"]).toString());
});
});
});
return app;
};
Deleting a secure store entry
For the deletion of the secure store entry in store “TEST_STORE” with key “TEST_VALUE”, procedure “SYS”.”USER_SECURESTORE_DELETE” is called the same way than the other procedures.
"use strict";
module.exports = function() {
var express = require("express");
var hdbext = require("@sap/hdbext");
var app = express.Router();
app.get('/', function(req, res) {
hdbext.loadProcedure(req.db, "SYS", "USER_SECURESTORE_DELETE", function(error, proc) {
if(error) {
res.send("Error during procedure loading:" + error.message);
return;
}
proc({"STORE_NAME":"TEST_STORE",
"FOR_XS_APPLICATIONUSER": false,
"KEY": "TEST_VALUE"}, function(error){
if(error) {
res.send("Error during procedure execution: " + error.message);
return;
}
res.send("Entry in secure store successfully deleted.");
});
});
});
return app;
};
Build and run the NodeJS application
After the coding part is finished the MTA can be build and the NodeJS application/module can be executed.
In this case the NodeJS application is running on port 51026.
Executing the /createSecureStoreEntry results in following:
Executing the same route again results in an error, because of a duplicate key:
Lets retrieve the secure store entry by route /retrieveSecureStoreEntry:
And finally delete it using route /deleteSecureStoreEntry:
No comments:
Post a Comment