C1 Module Proxy¶
Introduction¶
C1 Core requires a constant TCP connection for bidirectional communication. This is hard to do using standard webservers like Nginx
or Apache
. C1 Module Proxy is a small service taking over exactly this task. It connects to C1 Core and handles all communication with it.
Installation¶
Using Docker¶
C1 Module Proxy provides a Docker template image with Ubuntu Focal and C1 Module Proxy preinstalled. You can pull this image with:
docker pull gitit.de:5005/sensaru/c1/c1-module-proxy:latest
Within the image you just need to modify the settings and files in /etc/c1-module-proxy/
(or mount them).
Kubernetes¶
Here's a template you can use to deploy c1-module-proxy in Kubernetes. A secret with the C1 Core client certificates must exist in the namespace in c1-core-client-tls
:
apiVersion: v1
kind: Service
metadata:
labels:
app: c1-module-proxy
name: c1-module-proxy
namespace: <your namespace>
spec:
clusterIP: None
ports:
- port: 80
selector:
app: c1-module-proxy
--
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: c1-module-proxy
namespace: <your namespace>
spec:
replicas: 1
minReadySeconds: 60
selector:
matchLabels:
app: c1-module-proxy
strategy:
rollingUpdate:
maxSurge: 0
maxUnavailable: 1
type: RollingUpdate
template:
metadata:
labels:
app: c1-module-proxy
network-policy: permit-local-traffic
spec:
containers:
- name: c1-module-proxy
image: gitit.de:5005/sensaru/c1/c1-module-proxy:latest
ports:
- containerPort: 80
protocol: TCP
livenessProbe:
failureThreshold: 3
initialDelaySeconds: 60
periodSeconds: 30
successThreshold: 1
tcpSocket:
port: 80
timeoutSeconds: 1
readinessProbe:
failureThreshold: 3
initialDelaySeconds: 5
periodSeconds: 30
successThreshold: 1
tcpSocket:
port: 80
timeoutSeconds: 1
imagePullPolicy: Always
volumeMounts:
- name: c1-module-proxy-config
mountPath: /etc/c1-module-proxy/main.conf
subPath: main.conf
readOnly: true
- name: c1-core-client-tls
mountPath: /etc/c1-module-proxy/c1-core-client-tls
readOnly: true
imagePullSecrets:
- name: gitit-de
volumes:
- name: c1-module-proxy-config
configMap:
defaultMode: 288
optional: false
name: c1-module-proxy-config
- name: c1-core-client-tls
secret:
defaultMode: 292
optional: false
secretName: c1-core-client-tls
---
apiVersion: v1
kind: ConfigMap
binaryData:
main.conf: <base64-encoded main.conf>
metadata:
name: c1-module-proxy-config
namespace: <your namespace>
Install manually¶
Requirements¶
C1 Module Proxy requires
-
libhomegear-base
and -
libc1-module
.
In addition
GnuTLS
andGCrypt
are required.
libhomegear-base
can be downloaded as a Debian package for Debian and Ubuntu from https://downloads.homegear.eu/nightlies/. libc1-module
needs to be compiled manually.
Compilation of libc1-module
requires g++
, automake
and libtool
. Under Debian and Ubuntu install the packages automake, libtool and build-essential
.
To compile libc1-module
, clone the repository and execute:
cd /path/to/libc1-module
./makeRelease.sh
GnuTLS
and GCrypt
can be installed by installing the packages gnutls-dev
and libgcrypt-dev
.
c1-module-proxy
requires cmake
for compilation, so this package needs to be installed as well.
Compilation¶
To compile C1 Module Proxy, clone the repository and execute:
cd /path/to/c1-module-proxy
mkdir build
cd build
cmake ..
make -j4
strip c1-module-proxy
cp c1-module-proxy /usr/bin
cd ..
Now create the settings and log directory and copy the example configuration:
mkdir /etc/c1-module-proxy
cp misc/* /etc/c1-module-proxy
mkdir /var/log/c1
Create a user, e. g. c1
to run the service as user. Then set permissions on the log directory:
adduser --system --no-create-home --shell /bin/false --group c1
chown c1:c1 /var/log/c1
Configure module¶
Module configuration is also done in C1 Module Proxy. You can find the required entries in /etc/c1-module-proxy/main.conf
in section Module information
:
moduleName = {"de-DE": "Mein Modul", "en-US": "My module"}
moduleDescription = {"de-DE": "", "en-US": ""}
moduleUiMenu = {"label":{"de-DE":"Mein Modul","en-US":"My module"},"icon":"fontawesome/fa-heartbeat","url":"https://my-module.sensaru.net"}
moduleUiCategory =
moduleAppUiUrl =
moduleAppUiCategory =
moduleAclInfo = {"version":1,"rpcMethods":{"method1":"read","method2":"write"}}
moduleEventSubscriptions = ["c1-core/bp/+/au/+/association"]
moduleName
and moduleDescription
are self-explaining.
moduleUiMenu
is only required for modules providing an user interface for C1 Core. This creates a menu entry in C1 Core with the provided information.
moduleUiCategory
specifies under which category or heading the menu entry is sorted in.
moduleAppUiUrl
is only required for modules that provide an user interface for the inhabitant smartphone app. The specified page is opened when the module is selected in the app.
moduleAppUiCategory
specifies under which category the app entry is placed.
moduleAclInfo
is mandatory and contains the list of all RPC methods implemented by the module. Every method must be added to the property rpcMethods
and requires one of:
read
: To execute this method, read permissions are required.write
: To execute this method, write permissions are required.event
: To execute this method, event permissions are required.admin
: To execute this method, administrative permissions are required.
It is not required to specify REST methods here. REST methods are associated to the permissions above by their HTTP method. Only read
and write
are supported at the moment for REST methods.
Warning
When the ACL info is updated after the first registration, a restart of C1 Core is required after the update to the ACL info. The reason is that C1 Core regards this info as static information and caches it in memory.
moduleEventSubscriptions
contains all topics the module wants to subscribe to. See Events.
First connection¶
On first connection the module is registered to Sensaru Cloud. To be able to connect there is one more thing required: A client certificate. The client certificate needs to be requested from one of the certificate administrators. To create the certificate a module ID (e. g. c1-device-management
) needs to be defined. This ID is part of the common name of the signed certificate and this ID is used to identify the module within Sensaru Cloud.
Note
The common name is a base64-encoded JSON. The JSON looks like this:
{"type":"module","id":"c1-device-management","index":1,"date":1578005399878,"version":1,"environment":"dev"}
Place the certificate and key in the locations specified in /etc/c1-module-proxy/main.conf
(parameters coreClientCert
and coreClientKey
).
Warning
Never ever upload these certificates to a code repository and never ever store them within Docker images or containers. To use them within containers, use mounts.
High availability and scalability¶
Any module is required to be able to run in multiple instances over multiple datacenters. C1 Module Proxy and C1 Core already do a lot of the backend work, so you can start multiple instances of C1 Module Proxy. When multiple module instances are available, C1 Core randomly selects a module instance to direct requests to.
RPC¶
Requests originating from C1 Core¶
Your application needs to listen on the hostname and port specified in /etc/c1-module-proxy/main.conf
(parameters localRpcHost
and localRpcPort
) and handle JSON-RPC requests on an arbitrary path. The path C1 Module Proxy sends the requests to can be specified in /etc/c1-module-proxy/main.conf
(parameter localRpcPath
).
Every request coming from C1 Core is forwarded to your application. The response from your application is forwarded back to C1 Core.
Requests originating from your application¶
When you want to call a RPC method on C1 Core or another module, send this request to the listen address and the listen port specified in /etc/c1-module-proxy/main.conf
(parameters httpListenAddress
and httpListenPort
). The used path is irrelevant.
Calling moduleMethod¶
In contrast to the description here, when calling moduleMethod
the response is returned directly when C1 Module Proxy is used. This makes usage much easier. The underlying call is still asynchronous.
More information¶
For more information read the section about inter-module communication.
REST¶
First read the section about module REST API's.
C1 Module Proxy takes the tunneled RPC call and translates it back to a REST call including HTTP headers. The REST request is called on the same hostname and port as specified in localRpcHost
and localRpcPort
in /etc/c1-module-proxy/main.conf
.
The RPC metadata Struct which includes for example the logged in user and principal is passed as a base64-encoded JSON in the header field C1-VERIFIED-METADATA
. Like this you can control data access and only return data the user has access to.
An example header might look like this:
C1-VERIFIED-METADATA: eyJpYnNIb21lQ2xpZW50SWQiOiIiLCJpYnNIb21lQ2xpZW50VXNlcnMiOltdLCJzb3VyY2VNb2R1bGVJZCI6Imlicy1jb3JlIiwidXNlcklic0hvbWVDbGllbnRzIjp7ImFjdGl2ZSI6e30sImFsbCI6e319LCJ1c2VySWQiOnsiYnAiOiIiLCJpZCI6IjE1N2Q5MzUwLTFkYjgtMTFlOS04ZTY2LTJmNzFhMGJlNGNjNSIsInNkIjoiIiwic3AiOiI0ODEwOTM1MC0xZGI2LTExZTktOGU2Ni0yZjcxYTBiZTRjYzUiLCJ0eXBlIjoxfX0=
The decoded base64 from above contains the following JSON:
{
"homeClientId": "",
"homeClientUsers": [],
"sourceModuleId": "c1-core",
"userHomeClients": {
"active": {},
"all": {}
},
"userId": {
"bp": "",
"id": "157d9350-1db8-11e9-8e66-2f71a0be4cc5",
"sd": "",
"sp": "48109350-1db6-11e9-8e66-2f71a0be4cc5",
"type": 1
}
}
See the chapter about verified metadata for more information.
Authorization¶
It is mandatory that the logged-in user is only able to access data from it's own principal and data of principals that are hirarchically below the user's principal level (e. g. a system distributor user is allowed to access data from the system distributor's business partners).
Apart from that a user can be granted or denied permission
- per RPC method or
- per REST method/path combination.
Warning
Never ever allow a user access to data outside of it's own principal scope!
Permission types¶
There are four possible permission types that can be assigned to a RPC method:
read
write
event
isAdmin
For REST currently no permission types can be assigned manually to REST methods or paths. This is on our issue list. Currently permissions are set automatically in C1 Core. See below.
RPC permissions¶
The per RPC method permission is given by the group ACLs assigned to the user in C1 Core. This permission is checked within C1 Core, so there are no checks required within the module.
For this to work, the module must pass ACL information to C1 Core upon module registration. This ACL information is a JSON defined in main.conf
in C1 Module Proxy (setting moduleAclInfo
, see above).
REST permissions¶
REST permission types are assigned automatically based on the HTTP method
. The association is as follows:
HTTP method | Permission type |
---|---|
DELETE |
write |
GET |
read |
PATCH |
write |
POST |
write |
PUT |
write |
The permission is checked within C1 Core, so there are no checks required within the module.