add command line options for certificate management
This commit is contained in:
Родитель
e15d2a640e
Коммит
0d418c3d47
543
README.md
543
README.md
|
@ -281,249 +281,291 @@ The syntax of the configuration file is as follows:
|
|||
## Command line options
|
||||
The complete usage of the application can be shown using the `--help` command line option and is as follows:
|
||||
|
||||
Usage: opcpublisher.exe <applicationname> [<iothubconnectionstring>] [<options>]
|
||||
Current directory is: <current-directory>
|
||||
Log file is: <hostname>-publisher.log
|
||||
Log level is: info
|
||||
|
||||
OPC Edge Publisher to subscribe to configured OPC UA servers and send telemetry to Azure IoTHub.
|
||||
To exit the application, just press ENTER while it is running.
|
||||
Usage: opcpublisher.exe <applicationname> [<iothubconnectionstring>] [<options>]
|
||||
|
||||
applicationname: the OPC UA application name to use, required
|
||||
The application name is also used to register the publisher under this name in the
|
||||
IoTHub device registry.
|
||||
OPC Edge Publisher to subscribe to configured OPC UA servers and send telemetry to Azure IoTHub.
|
||||
To exit the application, just press CTRL-C while it is running.
|
||||
|
||||
iothubconnectionstring: the IoTHub owner connectionstring, optional
|
||||
applicationname: the OPC UA application name to use, required
|
||||
The application name is also used to register the publisher under this name in the
|
||||
IoTHub device registry.
|
||||
|
||||
There are a couple of environment variables which can be used to control the application:
|
||||
_HUB_CS: sets the IoTHub owner connectionstring
|
||||
_GW_LOGP: sets the filename of the log file to use
|
||||
_TPC_SP: sets the path to store certificates of trusted stations
|
||||
_GW_PNFP: sets the filename of the publishing configuration file
|
||||
iothubconnectionstring: the IoTHub owner connectionstring, optional
|
||||
|
||||
Command line arguments overrule environment variable settings.
|
||||
There are a couple of environment variables which can be used to control the application:
|
||||
_HUB_CS: sets the IoTHub owner connectionstring
|
||||
_GW_LOGP: sets the filename of the log file to use
|
||||
_TPC_SP: sets the path to store certificates of trusted stations
|
||||
_GW_PNFP: sets the filename of the publishing configuration file
|
||||
|
||||
Options:
|
||||
--pf, --publishfile=VALUE
|
||||
the filename to configure the nodes to publish.
|
||||
Default: '/appdata/publishednodes.json'
|
||||
--tc, --telemetryconfigfile=VALUE
|
||||
the filename to configure the ingested telemetry
|
||||
Default: ''
|
||||
-s, --site=VALUE the site OPC Publisher is working in. if specified
|
||||
this domain is appended (delimited by a ':' to
|
||||
the 'ApplicationURI' property when telemetry is
|
||||
sent to IoTHub.
|
||||
The value must follow the syntactical rules of a
|
||||
DNS hostname.
|
||||
Default: not set
|
||||
--sd, --shopfloordomain=VALUE
|
||||
same as site option, only there for backward
|
||||
compatibility
|
||||
The value must follow the syntactical rules of a
|
||||
DNS hostname.
|
||||
Default: not set
|
||||
--ic, --iotcentral publisher will send OPC UA data in IoTCentral
|
||||
compatible format (DisplayName of a node is used
|
||||
as key, this key is the Field name in IoTCentral)
|
||||
. you need to ensure that all DisplayName's are
|
||||
unique. (Auto enables fetch display name)
|
||||
Default: False
|
||||
--sw, --sessionconnectwait=VALUE
|
||||
specify the wait time in seconds publisher is
|
||||
trying to connect to disconnected endpoints and
|
||||
starts monitoring unmonitored items
|
||||
Min: 10
|
||||
Default: 10
|
||||
--mq, --monitoreditemqueuecapacity=VALUE
|
||||
specify how many notifications of monitored items
|
||||
can be stored in the internal queue, if the data
|
||||
can not be sent quick enough to IoTHub
|
||||
Min: 1024
|
||||
Default: 8192
|
||||
--di, --diagnosticsinterval=VALUE
|
||||
shows publisher diagnostic info at the specified
|
||||
interval in seconds (need log level info). 0
|
||||
disables diagnostic output.
|
||||
Default: 0
|
||||
--vc, --verboseconsole=VALUE
|
||||
ignored, only supported for backward comaptibility.
|
||||
--ns, --noshutdown=VALUE
|
||||
same as runforever.
|
||||
Default: False
|
||||
--rf, --runforwver publisher can not be stopped by pressing a key on
|
||||
the console, but will run forever.
|
||||
Default: False
|
||||
--lf, --logfile=VALUE the filename of the logfile to use.
|
||||
Default: './<hostname>-publisher.log'
|
||||
--lt, --logflushtimespan=VALUE
|
||||
the timespan in seconds when the logfile should be
|
||||
flushed.
|
||||
Default: on logfile rollover.
|
||||
--ll, --loglevel=VALUE the loglevel to use (allowed: fatal, error, warn,
|
||||
info, debug, verbose).
|
||||
Default: info
|
||||
--ih, --iothubprotocol=VALUE
|
||||
the protocol to use for communication with Azure
|
||||
IoTHub (allowed values: Amqp, Http1, Amqp_
|
||||
WebSocket_Only, Amqp_Tcp_Only, Mqtt, Mqtt_
|
||||
WebSocket_Only, Mqtt_Tcp_Only).
|
||||
Default: Mqtt_WebSocket_Only
|
||||
--ms, --iothubmessagesize=VALUE
|
||||
the max size of a message which can be send to
|
||||
IoTHub. when telemetry of this size is available
|
||||
it will be sent.
|
||||
0 will enforce immediate send when telemetry is
|
||||
available
|
||||
Min: 0
|
||||
Max: 262144
|
||||
Default: 262144
|
||||
--si, --iothubsendinterval=VALUE
|
||||
the interval in seconds when telemetry should be
|
||||
send to IoTHub. If 0, then only the
|
||||
iothubmessagesize parameter controls when
|
||||
telemetry is sent.
|
||||
Default: '10'
|
||||
--dc, --deviceconnectionstring=VALUE
|
||||
if publisher is not able to register itself with
|
||||
IoTHub, you can create a device with name <
|
||||
applicationname> manually and pass in the
|
||||
connectionstring of this device.
|
||||
Default: none
|
||||
-c, --connectionstring=VALUE
|
||||
the IoTHub owner connectionstring.
|
||||
Default: none
|
||||
--pn, --portnum=VALUE the server port of the publisher OPC server
|
||||
endpoint.
|
||||
Default: 62222
|
||||
--pa, --path=VALUE the enpoint URL path part of the publisher OPC
|
||||
server endpoint.
|
||||
Default: '/UA/Publisher'
|
||||
--lr, --ldsreginterval=VALUE
|
||||
the LDS(-ME) registration interval in ms. If 0,
|
||||
then the registration is disabled.
|
||||
Default: 0
|
||||
--ol, --opcmaxstringlen=VALUE
|
||||
the max length of a string opc can transmit/
|
||||
receive.
|
||||
Default: 1048576
|
||||
--ot, --operationtimeout=VALUE
|
||||
the operation timeout of the publisher OPC UA
|
||||
client in ms.
|
||||
Default: 120000
|
||||
--oi, --opcsamplinginterval=VALUE
|
||||
the publisher is using this as default value in
|
||||
milliseconds to request the servers to sample
|
||||
the nodes with this interval
|
||||
this value might be revised by the OPC UA
|
||||
servers to a supported sampling interval.
|
||||
please check the OPC UA specification for
|
||||
details how this is handled by the OPC UA stack.
|
||||
a negative value will set the sampling interval
|
||||
to the publishing interval of the subscription
|
||||
this node is on.
|
||||
0 will configure the OPC UA server to sample in
|
||||
the highest possible resolution and should be
|
||||
taken with care.
|
||||
Default: 1000
|
||||
--op, --opcpublishinginterval=VALUE
|
||||
the publisher is using this as default value in
|
||||
milliseconds for the publishing interval setting
|
||||
of the subscriptions established to the OPC UA
|
||||
servers.
|
||||
please check the OPC UA specification for
|
||||
details how this is handled by the OPC UA stack.
|
||||
a value less than or equal zero will let the
|
||||
server revise the publishing interval.
|
||||
Default: 0
|
||||
--ct, --createsessiontimeout=VALUE
|
||||
specify the timeout in seconds used when creating
|
||||
a session to an endpoint. On unsuccessful
|
||||
connection attemps a backoff up to 5 times the
|
||||
specified timeout value is used.
|
||||
Min: 1
|
||||
Default: 10
|
||||
--ki, --keepaliveinterval=VALUE
|
||||
specify the interval in seconds the publisher is
|
||||
sending keep alive messages to the OPC servers
|
||||
on the endpoints it is connected to.
|
||||
Min: 2
|
||||
Default: 2
|
||||
--kt, --keepalivethreshold=VALUE
|
||||
specify the number of keep alive packets a server
|
||||
can miss, before the session is disconneced
|
||||
Min: 1
|
||||
Default: 5
|
||||
--st, --opcstacktracemask=VALUE
|
||||
ignored, only supported for backward comaptibility.
|
||||
--as, --autotrustservercerts=VALUE
|
||||
same as autoaccept, only supported for backward
|
||||
cmpatibility.
|
||||
Default: False
|
||||
--aa, --autoaccept the publisher trusts all servers it is
|
||||
establishing a connection to.
|
||||
Default: False
|
||||
--tm, --trustmyself=VALUE
|
||||
same as trustowncert.
|
||||
Default: True
|
||||
--to, --trustowncert the publisher certificate is put into the trusted
|
||||
certificate store automatically.
|
||||
Default: True
|
||||
--fd, --fetchdisplayname=VALUE
|
||||
same as fetchname.
|
||||
Default: False
|
||||
--fn, --fetchname=VALUE
|
||||
enable to read the display name of a published
|
||||
node from the server. this will increase the
|
||||
runtime.
|
||||
Default: False
|
||||
--at, --appcertstoretype=VALUE
|
||||
the own application cert store type.
|
||||
(allowed values: Directory, X509Store)
|
||||
Default: 'X509Store'
|
||||
--ap, --appcertstorepath=VALUE
|
||||
the path where the own application cert should be
|
||||
stored
|
||||
Default (depends on store type):
|
||||
X509Store: 'CurrentUser\UA_MachineDefault'
|
||||
Directory: 'CertificateStores/own'
|
||||
--tt, --trustedcertstoretype=VALUE
|
||||
the trusted cert store type.
|
||||
(allowed values: Directory, X509Store)
|
||||
Default: Directory
|
||||
--tp, --trustedcertstorepath=VALUE
|
||||
the path of the trusted cert store
|
||||
Default (depends on store type):
|
||||
X509Store: 'CurrentUser\UA_MachineDefault'
|
||||
Directory: 'CertificateStores/trusted'
|
||||
--rt, --rejectedcertstoretype=VALUE
|
||||
the rejected cert store type.
|
||||
(allowed values: Directory, X509Store)
|
||||
Default: Directory
|
||||
--rp, --rejectedcertstorepath=VALUE
|
||||
the path of the rejected cert store
|
||||
Default (depends on store type):
|
||||
X509Store: 'CurrentUser\UA_MachineDefault'
|
||||
Directory: 'CertificateStores/rejected'
|
||||
--it, --issuercertstoretype=VALUE
|
||||
the trusted issuer cert store type.
|
||||
(allowed values: Directory, X509Store)
|
||||
Default: Directory
|
||||
--ip, --issuercertstorepath=VALUE
|
||||
the path of the trusted issuer cert store
|
||||
Default (depends on store type):
|
||||
X509Store: 'CurrentUser\UA_MachineDefault'
|
||||
Directory: 'CertificateStores/issuers'
|
||||
--dt, --devicecertstoretype=VALUE
|
||||
the iothub device cert store type.
|
||||
(allowed values: Directory, X509Store)
|
||||
Default: X509Store
|
||||
--dp, --devicecertstorepath=VALUE
|
||||
the path of the iot device cert store
|
||||
Default Default (depends on store type):
|
||||
X509Store: 'My'
|
||||
Directory: 'CertificateStores/IoTHub'
|
||||
-i, --install register OPC Publisher with IoTHub and then exits.
|
||||
Default: False
|
||||
-h, --help show this message and exit
|
||||
Command line arguments overrule environment variable settings.
|
||||
|
||||
Options:
|
||||
--pf, --publishfile=VALUE
|
||||
the filename to configure the nodes to publish.
|
||||
Default: './publishednodes.json'
|
||||
--tc, --telemetryconfigfile=VALUE
|
||||
the filename to configure the ingested telemetry
|
||||
Default: ''
|
||||
-s, --site=VALUE the site OPC Publisher is working in. if specified
|
||||
this domain is appended (delimited by a ':' to
|
||||
the 'ApplicationURI' property when telemetry is
|
||||
sent to IoTHub.
|
||||
The value must follow the syntactical rules of a
|
||||
DNS hostname.
|
||||
Default: not set
|
||||
--ic, --iotcentral publisher will send OPC UA data in IoTCentral
|
||||
compatible format (DisplayName of a node is used
|
||||
as key, this key is the Field name in IoTCentral)
|
||||
. you need to ensure that all DisplayName's are
|
||||
unique. (Auto enables fetch display name)
|
||||
Default: False
|
||||
--sw, --sessionconnectwait=VALUE
|
||||
specify the wait time in seconds publisher is
|
||||
trying to connect to disconnected endpoints and
|
||||
starts monitoring unmonitored items
|
||||
Min: 10
|
||||
Default: 10
|
||||
--mq, --monitoreditemqueuecapacity=VALUE
|
||||
specify how many notifications of monitored items
|
||||
can be stored in the internal queue, if the data
|
||||
can not be sent quick enough to IoTHub
|
||||
Min: 1024
|
||||
Default: 8192
|
||||
--di, --diagnosticsinterval=VALUE
|
||||
shows publisher diagnostic info at the specified
|
||||
interval in seconds (need log level info). 0
|
||||
disables diagnostic output.
|
||||
Default: 0
|
||||
--ns, --noshutdown=VALUE
|
||||
same as runforever.
|
||||
Default: False
|
||||
--rf, --runforever publisher can not be stopped by pressing a key on
|
||||
the console, but will run forever.
|
||||
Default: False
|
||||
--lf, --logfile=VALUE the filename of the logfile to use.
|
||||
Default: './<hostname>-publisher.log'
|
||||
--lt, --logflushtimespan=VALUE
|
||||
the timespan in seconds when the logfile should be
|
||||
flushed.
|
||||
Default: 00:00:30 sec
|
||||
--ll, --loglevel=VALUE the loglevel to use (allowed: fatal, error, warn,
|
||||
info, debug, verbose).
|
||||
Default: info
|
||||
--ih, --iothubprotocol=VALUE
|
||||
the protocol to use for communication with Azure
|
||||
IoTHub (allowed values: Amqp, Http1, Amqp_
|
||||
WebSocket_Only, Amqp_Tcp_Only, Mqtt, Mqtt_
|
||||
WebSocket_Only, Mqtt_Tcp_Only).
|
||||
Default: Mqtt_WebSocket_Only
|
||||
--ms, --iothubmessagesize=VALUE
|
||||
the max size of a message which can be send to
|
||||
IoTHub. when telemetry of this size is available
|
||||
it will be sent.
|
||||
0 will enforce immediate send when telemetry is
|
||||
available
|
||||
Min: 0
|
||||
Max: 262144
|
||||
Default: 262144
|
||||
--si, --iothubsendinterval=VALUE
|
||||
the interval in seconds when telemetry should be
|
||||
send to IoTHub. If 0, then only the
|
||||
iothubmessagesize parameter controls when
|
||||
telemetry is sent.
|
||||
Default: '10'
|
||||
--dc, --deviceconnectionstring=VALUE
|
||||
if publisher is not able to register itself with
|
||||
IoTHub, you can create a device with name <
|
||||
applicationname> manually and pass in the
|
||||
connectionstring of this device.
|
||||
Default: none
|
||||
-c, --connectionstring=VALUE
|
||||
the IoTHub owner connectionstring.
|
||||
Default: none
|
||||
--pn, --portnum=VALUE the server port of the publisher OPC server
|
||||
endpoint.
|
||||
Default: 62222
|
||||
--pa, --path=VALUE the enpoint URL path part of the publisher OPC
|
||||
server endpoint.
|
||||
Default: '/UA/Publisher'
|
||||
--lr, --ldsreginterval=VALUE
|
||||
the LDS(-ME) registration interval in ms. If 0,
|
||||
then the registration is disabled.
|
||||
Default: 0
|
||||
--ol, --opcmaxstringlen=VALUE
|
||||
the max length of a string opc can transmit/
|
||||
receive.
|
||||
Default: 4194304
|
||||
--ot, --operationtimeout=VALUE
|
||||
the operation timeout of the publisher OPC UA
|
||||
client in ms.
|
||||
Default: 120000
|
||||
--oi, --opcsamplinginterval=VALUE
|
||||
the publisher is using this as default value in
|
||||
milliseconds to request the servers to sample
|
||||
the nodes with this interval
|
||||
this value might be revised by the OPC UA
|
||||
servers to a supported sampling interval.
|
||||
please check the OPC UA specification for
|
||||
details how this is handled by the OPC UA stack.
|
||||
a negative value will set the sampling interval
|
||||
to the publishing interval of the subscription
|
||||
this node is on.
|
||||
0 will configure the OPC UA server to sample in
|
||||
the highest possible resolution and should be
|
||||
taken with care.
|
||||
Default: 1000
|
||||
--op, --opcpublishinginterval=VALUE
|
||||
the publisher is using this as default value in
|
||||
milliseconds for the publishing interval setting
|
||||
of the subscriptions established to the OPC UA
|
||||
servers.
|
||||
please check the OPC UA specification for
|
||||
details how this is handled by the OPC UA stack.
|
||||
a value less than or equal zero will let the
|
||||
server revise the publishing interval.
|
||||
Default: 0
|
||||
--ct, --createsessiontimeout=VALUE
|
||||
specify the timeout in seconds used when creating
|
||||
a session to an endpoint. On unsuccessful
|
||||
connection attemps a backoff up to 5 times the
|
||||
specified timeout value is used.
|
||||
Min: 1
|
||||
Default: 10
|
||||
--ki, --keepaliveinterval=VALUE
|
||||
specify the interval in seconds the publisher is
|
||||
sending keep alive messages to the OPC servers
|
||||
on the endpoints it is connected to.
|
||||
Min: 2
|
||||
Default: 2
|
||||
--kt, --keepalivethreshold=VALUE
|
||||
specify the number of keep alive packets a server
|
||||
can miss, before the session is disconneced
|
||||
Min: 1
|
||||
Default: 5
|
||||
--aa, --autoaccept the publisher trusts all servers it is
|
||||
establishing a connection to.
|
||||
Default: False
|
||||
--tm, --trustmyself=VALUE
|
||||
same as trustowncert.
|
||||
Default: False
|
||||
--to, --trustowncert the publisher certificate is put into the trusted
|
||||
certificate store automatically.
|
||||
Default: False
|
||||
--fd, --fetchdisplayname=VALUE
|
||||
same as fetchname.
|
||||
Default: False
|
||||
--fn, --fetchname enable to read the display name of a published
|
||||
node from the server. this will increase the
|
||||
runtime.
|
||||
Default: False
|
||||
--at, --appcertstoretype=VALUE
|
||||
the own application cert store type.
|
||||
(allowed values: Directory, X509Store)
|
||||
Default: 'Directory'
|
||||
--ap, --appcertstorepath=VALUE
|
||||
the path where the own application cert should be
|
||||
stored
|
||||
Default (depends on store type):
|
||||
X509Store: 'CurrentUser\UA_MachineDefault'
|
||||
Directory: 'pki/own'
|
||||
--tp, --trustedcertstorepath=VALUE
|
||||
the path of the trusted cert store
|
||||
Default: 'pki/trusted'
|
||||
--rp, --rejectedcertstorepath=VALUE
|
||||
the path of the rejected cert store
|
||||
Default 'pki/rejected'
|
||||
--ip, --issuercertstorepath=VALUE
|
||||
the path of the trusted issuer cert store
|
||||
Default 'pki/issuer'
|
||||
--csr show data to create a certificate signing request
|
||||
Default 'False'
|
||||
--ab, --applicationcertbase64=VALUE
|
||||
update/set this applications certificate with the
|
||||
certificate passed in as bas64 string
|
||||
--af, --applicationcertfile=VALUE
|
||||
update/set this applications certificate with the
|
||||
certificate file specified
|
||||
--pb, --privatekeybase64=VALUE
|
||||
initial provisioning of the application
|
||||
certificate (with a PEM or PFX fomat) requires a
|
||||
private key passed in as base64 string
|
||||
--pk, --privatekeyfile=VALUE
|
||||
initial provisioning of the application
|
||||
certificate (with a PEM or PFX fomat) requires a
|
||||
private key passed in as file
|
||||
--cp, --certpassword=VALUE
|
||||
the optional password for the PEM or PFX or the
|
||||
installed application certificate
|
||||
--tb, --addtrustedcertbase64=VALUE
|
||||
adds the certificate to the applications trusted
|
||||
cert store passed in as base64 string (multiple
|
||||
strings supported)
|
||||
--tf, --addtrustedcertfile=VALUE
|
||||
adds the certificate file(s) to the applications
|
||||
trusted cert store passed in as base64 string (
|
||||
multiple filenames supported)
|
||||
--ib, --addissuercertbase64=VALUE
|
||||
adds the specified issuer certificate to the
|
||||
applications trusted issuer cert store passed in
|
||||
as base64 string (multiple strings supported)
|
||||
--if, --addissuercertfile=VALUE
|
||||
adds the specified issuer certificate file(s) to
|
||||
the applications trusted issuer cert store (
|
||||
multiple filenames supported)
|
||||
--rb, --updatecrlbase64=VALUE
|
||||
update the CRL passed in as base64 string to the
|
||||
corresponding cert store (trusted or trusted
|
||||
issuer)
|
||||
--uc, --updatecrlfile=VALUE
|
||||
update the CRL passed in as file to the
|
||||
corresponding cert store (trusted or trusted
|
||||
issuer)
|
||||
--rc, --removecert=VALUE
|
||||
remove cert(s) with the given thumbprint(s) (
|
||||
multiple thumbprints supported)
|
||||
--dt, --devicecertstoretype=VALUE
|
||||
the iothub device cert store type.
|
||||
(allowed values: Directory, X509Store)
|
||||
Default: X509Store
|
||||
--dp, --devicecertstorepath=VALUE
|
||||
the path of the iot device cert store
|
||||
Default Default (depends on store type):
|
||||
X509Store: 'My'
|
||||
Directory: 'CertificateStores/IoTHub'
|
||||
-i, --install register OPC Publisher with IoTHub and then exits.
|
||||
Default: False
|
||||
-h, --help show this message and exit
|
||||
--st, --opcstacktracemask=VALUE
|
||||
ignored, only supported for backward comaptibility.
|
||||
--sd, --shopfloordomain=VALUE
|
||||
same as site option, only there for backward
|
||||
compatibility
|
||||
The value must follow the syntactical rules of a
|
||||
DNS hostname.
|
||||
Default: not set
|
||||
--vc, --verboseconsole=VALUE
|
||||
ignored, only supported for backward comaptibility.
|
||||
--as, --autotrustservercerts=VALUE
|
||||
same as autoaccept, only supported for backward
|
||||
cmpatibility.
|
||||
Default: False
|
||||
--tt, --trustedcertstoretype=VALUE
|
||||
ignored, only supported for backward compatibility.
|
||||
the trusted cert store will always reside in a
|
||||
directory.
|
||||
--rt, --rejectedcertstoretype=VALUE
|
||||
ignored, only supported for backward compatibility.
|
||||
the rejected cert store will always reside in a
|
||||
directory.
|
||||
--it, --issuercertstoretype=VALUE
|
||||
ignored, only supported for backward compatibility.
|
||||
the trusted issuer cert store will always
|
||||
reside in a directory.
|
||||
|
||||
Typically you specify the IoTHub owner connectionstring only on the first start of the application. The connectionstring will be encrypted and stored in the platforms certificiate store.
|
||||
On subsequent calls it will be read from there and reused. If you specify the connectionstring on each start, the device which is created for the application in the IoTHub device registry will be removed and recreated each time.
|
||||
|
@ -661,7 +703,34 @@ OPC Publisher uses the hostname of the machine is running on for certificate and
|
|||
docker run -h publisher mcr.microsoft.com/iotedge/opc-publisher <applicationname> [<iothubconnectionstring>] [options]
|
||||
|
||||
### Using bind mounts (shared filesystem)
|
||||
In certain use cases it may make sense to read configuration information from or write log files to locations on the host and not keep them in the container file system only. To achieve this you need to use the `-v` option of `docker run` in the bind mount mode.
|
||||
In certain use cases it may make sense to read configuration information from or write log files to locations on the host and not keep
|
||||
them in the container file system only. To achieve this you need to use the `-v` option of `docker run` in the bind mount mode.
|
||||
|
||||
## OPC UA X.509 certificates
|
||||
As you know, OPC UA is using X.509 certificates to authenticate OPC UA client and server during establishment of a connection and
|
||||
to encrypt the communication between the two parties.
|
||||
OPC Publisher does use certificate stores maintained by the OPC UA stack to manage all certificates.
|
||||
On startup OPC Publisher checks if there is a certificate for itself (see `InitApplicationSecurityAsync`
|
||||
in `OpcApplicationConfigurationSecurity.cs`) and creates a self-signed certificate if there is none or if there is not one passed in
|
||||
via command line options.
|
||||
Self-signed certificates do not provide any security, since they are not signed by a trusted CA.
|
||||
OPC Publisher does provide several command line options to:
|
||||
* retrieve CSR information of the current application certificate used by OPC Publisher
|
||||
* provision OPC Publisher with a CA signed certificate
|
||||
* provision OPC Publisher with a new key pair and matching CA signed certificate
|
||||
* add certificates to the trusted peer or trusted issuer cert store with certificates from an OPC UA application or from a CA
|
||||
* add a CRL
|
||||
* remove a certificate from the trusted peer or trusted issuers cert store
|
||||
|
||||
All these options allow to pass in parameters via files or base64 encoded strings.
|
||||
|
||||
The default store type for all cert stores is the file system. You can change that via command line options. Especially when you run OPC Publisher
|
||||
in a container, then the persistency of the certificates is important, since the container does not provide persistency. You need to use docker's `-v` option
|
||||
to persist the certificate stores in the host file system or a docker volume. If you are use a docker volume, you can pass in certificate relevant
|
||||
data via base64 encoded strings.
|
||||
|
||||
If you want to see how a CA signed certificate can be used, please open an issue on this repo and we follow up.
|
||||
|
||||
|
||||
## Performance and memory considerations
|
||||
### Commandline parameters contolling performance and memory
|
||||
|
|
|
@ -15,10 +15,10 @@ namespace OpcPublisher
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using static OpcApplicationConfiguration;
|
||||
using static OpcPublisher.OpcMonitoredItem;
|
||||
using static OpcPublisher.PublisherNodeConfiguration;
|
||||
using static OpcPublisher.PublisherTelemetryConfiguration;
|
||||
using static OpcStackConfiguration;
|
||||
using static Program;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace OpcPublisher
|
|||
using Microsoft.Azure.Devices.Client;
|
||||
using Opc.Ua;
|
||||
using System;
|
||||
using static OpcStackConfiguration;
|
||||
using static OpcApplicationConfiguration;
|
||||
using static Program;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -0,0 +1,240 @@
|
|||
|
||||
using Opc.Ua;
|
||||
using System;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace OpcPublisher
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static Opc.Ua.CertificateStoreType;
|
||||
using static Program;
|
||||
|
||||
/// <summary>
|
||||
/// Class for OPC Application configuration.
|
||||
/// </summary>
|
||||
public partial class OpcApplicationConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration info for the OPC application.
|
||||
/// </summary>
|
||||
public static ApplicationConfiguration ApplicationConfiguration { get; private set; }
|
||||
public static string Hostname
|
||||
{
|
||||
get => _hostname;
|
||||
set => _hostname = value.ToLowerInvariant();
|
||||
}
|
||||
|
||||
public static string ApplicationName { get; set; } = "publisher";
|
||||
public static string ApplicationUri => $"urn:{Hostname}:{ApplicationName}:microsoft:";
|
||||
public static string ProductUri => $"https://github.com/azure-samples/iot-edge-opc-publisher";
|
||||
public static ushort ServerPort { get; set; } = 62222;
|
||||
public static string ServerPath { get; set; } = "/UA/Publisher";
|
||||
|
||||
/// <summary>
|
||||
/// Default endpoint security of the application.
|
||||
/// </summary>
|
||||
public static string ServerSecurityPolicy { get; set; } = SecurityPolicies.Basic128Rsa15;
|
||||
|
||||
/// <summary>
|
||||
/// Enables unsecure endpoint access to the application.
|
||||
/// </summary>
|
||||
public static bool EnableUnsecureTransport { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the LDS registration interval.
|
||||
/// </summary>
|
||||
public static int LdsRegistrationInterval { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Set the max string length the OPC stack supports.
|
||||
/// </summary>
|
||||
public static int OpcMaxStringLength { get; set; } = 4 * 1024 * 1024;
|
||||
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// Mapping of the application logging levels to OPC stack logging levels.
|
||||
/// </summary>
|
||||
public static int OpcTraceToLoggerVerbose = 0;
|
||||
public static int OpcTraceToLoggerDebug = 0;
|
||||
public static int OpcTraceToLoggerInformation = 0;
|
||||
public static int OpcTraceToLoggerWarning = 0;
|
||||
public static int OpcTraceToLoggerError = 0;
|
||||
public static int OpcTraceToLoggerFatal = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Set the OPC stack log level.
|
||||
/// </summary>
|
||||
public static int OpcStackTraceMask { get; set; } = Utils.TraceMasks.Error | Utils.TraceMasks.Security | Utils.TraceMasks.StackTrace | Utils.TraceMasks.StartStop;
|
||||
|
||||
/// <summary>
|
||||
/// Timeout for OPC operations.
|
||||
/// </summary>
|
||||
public static int OpcOperationTimeout { get; set; } = 120000;
|
||||
|
||||
|
||||
public static bool OpcPublisherAutoTrustServerCerts { get; set; } = false;
|
||||
|
||||
public static uint OpcSessionCreationTimeout { get; set; } = 10;
|
||||
|
||||
public static uint OpcSessionCreationBackoffMax { get; set; } = 5;
|
||||
|
||||
public static uint OpcKeepAliveDisconnectThreshold { get; set; } = 5;
|
||||
|
||||
public static int OpcKeepAliveIntervalInSec { get; set; } = 2;
|
||||
|
||||
|
||||
public const int OpcSamplingIntervalDefault = 1000;
|
||||
|
||||
public static int OpcSamplingInterval { get; set; } = OpcSamplingIntervalDefault;
|
||||
|
||||
|
||||
public const int OpcPublishingIntervalDefault = 0;
|
||||
|
||||
public static int OpcPublishingInterval { get; set; } = OpcPublishingIntervalDefault;
|
||||
|
||||
public static string PublisherServerSecurityPolicy { get; set; } = SecurityPolicies.Basic128Rsa15;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Ctor of the OPC application configuration.
|
||||
/// </summary>
|
||||
public OpcApplicationConfiguration()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures all OPC stack settings.
|
||||
/// </summary>
|
||||
public async Task<ApplicationConfiguration> ConfigureAsync()
|
||||
{
|
||||
// instead of using a configuration XML file, we configure everything programmatically
|
||||
|
||||
// passed in as command line argument
|
||||
ApplicationConfiguration = new ApplicationConfiguration();
|
||||
ApplicationConfiguration.ApplicationName = ApplicationName;
|
||||
ApplicationConfiguration.ApplicationUri = ApplicationUri;
|
||||
ApplicationConfiguration.ProductUri = ProductUri;
|
||||
ApplicationConfiguration.ApplicationType = ApplicationType.Server;
|
||||
|
||||
// configure OPC stack tracing
|
||||
ApplicationConfiguration.TraceConfiguration = new TraceConfiguration();
|
||||
ApplicationConfiguration.TraceConfiguration.TraceMasks = OpcStackTraceMask;
|
||||
ApplicationConfiguration.TraceConfiguration.ApplySettings();
|
||||
Utils.Tracing.TraceEventHandler += new EventHandler<TraceEventArgs>(LoggerOpcUaTraceHandler);
|
||||
Logger.Information($"opcstacktracemask set to: 0x{OpcStackTraceMask:X}");
|
||||
|
||||
// configure transport settings
|
||||
ApplicationConfiguration.TransportQuotas = new TransportQuotas();
|
||||
ApplicationConfiguration.TransportQuotas.MaxStringLength = OpcMaxStringLength;
|
||||
ApplicationConfiguration.TransportQuotas.MaxMessageSize = 4 * 1024 * 1024;
|
||||
|
||||
// configure OPC UA server
|
||||
ApplicationConfiguration.ServerConfiguration = new ServerConfiguration();
|
||||
|
||||
// configure server base addresses
|
||||
if (ApplicationConfiguration.ServerConfiguration.BaseAddresses.Count == 0)
|
||||
{
|
||||
// we do not use the localhost replacement mechanism of the configuration loading, to immediately show the base address here
|
||||
ApplicationConfiguration.ServerConfiguration.BaseAddresses.Add($"opc.tcp://{Hostname}:{ServerPort}{ServerPath}");
|
||||
}
|
||||
foreach (var endpoint in ApplicationConfiguration.ServerConfiguration.BaseAddresses)
|
||||
{
|
||||
Logger.Information($"OPC UA server base address: {endpoint}");
|
||||
}
|
||||
|
||||
// by default use high secure transport
|
||||
ServerSecurityPolicy newPolicy = new ServerSecurityPolicy()
|
||||
{
|
||||
SecurityMode = MessageSecurityMode.SignAndEncrypt,
|
||||
SecurityPolicyUri = SecurityPolicies.Basic256Sha256
|
||||
};
|
||||
ApplicationConfiguration.ServerConfiguration.SecurityPolicies.Add(newPolicy);
|
||||
Logger.Information($"Security policy {newPolicy.SecurityPolicyUri} with mode {newPolicy.SecurityMode} added");
|
||||
|
||||
// add none secure transport on request
|
||||
if (EnableUnsecureTransport)
|
||||
{
|
||||
newPolicy = new ServerSecurityPolicy()
|
||||
{
|
||||
SecurityMode = MessageSecurityMode.None,
|
||||
SecurityPolicyUri = SecurityPolicies.None
|
||||
};
|
||||
ApplicationConfiguration.ServerConfiguration.SecurityPolicies.Add(newPolicy);
|
||||
Logger.Information($"Unsecure security policy {newPolicy.SecurityPolicyUri} with mode {newPolicy.SecurityMode} added");
|
||||
Logger.Warning($"Note: This is a security risk and needs to be disabled for production use");
|
||||
}
|
||||
|
||||
// security configuration
|
||||
await InitApplicationSecurityAsync();
|
||||
|
||||
// set LDS registration interval
|
||||
ApplicationConfiguration.ServerConfiguration.MaxRegistrationInterval = LdsRegistrationInterval;
|
||||
Logger.Information($"LDS(-ME) registration intervall set to {LdsRegistrationInterval} ms (0 means no registration)");
|
||||
|
||||
// show certificate store information
|
||||
await ShowCertificateStoreInformationAsync();
|
||||
|
||||
return ApplicationConfiguration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event handler to log OPC UA stack trace messages into own logger.
|
||||
/// </summary>
|
||||
private static void LoggerOpcUaTraceHandler(object sender, TraceEventArgs e)
|
||||
{
|
||||
// return fast if no trace needed
|
||||
if ((e.TraceMask & OpcStackTraceMask) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// e.Exception and e.Message are always null
|
||||
|
||||
// format the trace message
|
||||
string message = string.Empty;
|
||||
message = string.Format(e.Format, e.Arguments).Trim();
|
||||
message = "OPC: " + message;
|
||||
|
||||
// map logging level
|
||||
if ((e.TraceMask & OpcTraceToLoggerVerbose) != 0)
|
||||
{
|
||||
Logger.Verbose(message);
|
||||
return;
|
||||
}
|
||||
if ((e.TraceMask & OpcTraceToLoggerDebug) != 0)
|
||||
{
|
||||
Logger.Debug(message);
|
||||
return;
|
||||
}
|
||||
if ((e.TraceMask & OpcTraceToLoggerInformation) != 0)
|
||||
{
|
||||
Logger.Information(message);
|
||||
return;
|
||||
}
|
||||
if ((e.TraceMask & OpcTraceToLoggerWarning) != 0)
|
||||
{
|
||||
Logger.Warning(message);
|
||||
return;
|
||||
}
|
||||
if ((e.TraceMask & OpcTraceToLoggerError) != 0)
|
||||
{
|
||||
Logger.Error(message);
|
||||
return;
|
||||
}
|
||||
if ((e.TraceMask & OpcTraceToLoggerFatal) != 0)
|
||||
{
|
||||
Logger.Fatal(message);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private static string _hostname = $"{Utils.GetHostName().ToLowerInvariant()}";
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -14,7 +14,7 @@ namespace OpcPublisher
|
|||
using static HubCommunication;
|
||||
using static OpcPublisher.OpcMonitoredItem;
|
||||
using static OpcPublisher.PublisherTelemetryConfiguration;
|
||||
using static OpcStackConfiguration;
|
||||
using static OpcApplicationConfiguration;
|
||||
using static Program;
|
||||
using static PublisherNodeConfiguration;
|
||||
|
||||
|
@ -606,15 +606,15 @@ namespace OpcPublisher
|
|||
|
||||
// start connecting
|
||||
selectedEndpoint = CoreClientUtils.SelectEndpoint(EndpointUrl.AbsoluteUri, UseSecurity);
|
||||
configuredEndpoint = new ConfiguredEndpoint(null, selectedEndpoint, EndpointConfiguration.Create(PublisherOpcApplicationConfiguration));
|
||||
configuredEndpoint = new ConfiguredEndpoint(null, selectedEndpoint, EndpointConfiguration.Create(OpcApplicationConfiguration.ApplicationConfiguration));
|
||||
uint timeout = SessionTimeout * ((UnsuccessfulConnectionCount >= OpcSessionCreationBackoffMax) ? OpcSessionCreationBackoffMax : UnsuccessfulConnectionCount + 1);
|
||||
Logger.Information($"Create {(UseSecurity ? "secured" : "unsecured")} session for endpoint URI '{EndpointUrl.AbsoluteUri}' with timeout of {timeout} ms.");
|
||||
OpcUaClientSession = await Session.Create(
|
||||
PublisherOpcApplicationConfiguration,
|
||||
OpcApplicationConfiguration.ApplicationConfiguration,
|
||||
configuredEndpoint,
|
||||
true,
|
||||
false,
|
||||
PublisherOpcApplicationConfiguration.ApplicationName,
|
||||
OpcApplicationConfiguration.ApplicationConfiguration.ApplicationName,
|
||||
timeout,
|
||||
new UserIdentity(new AnonymousIdentityToken()),
|
||||
null);
|
||||
|
|
|
@ -1,367 +0,0 @@
|
|||
|
||||
using Opc.Ua;
|
||||
using System;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace OpcPublisher
|
||||
{
|
||||
using System.Threading.Tasks;
|
||||
using static Opc.Ua.CertificateStoreType;
|
||||
using static Program;
|
||||
|
||||
public class OpcStackConfiguration
|
||||
{
|
||||
public static ApplicationConfiguration PublisherOpcApplicationConfiguration { get; private set; }
|
||||
public static string ApplicationName { get; set; } = "publisher";
|
||||
|
||||
public static ushort PublisherServerPort { get; set; } = 62222;
|
||||
|
||||
public static string PublisherServerPath { get; set; } = "/UA/Publisher";
|
||||
|
||||
public static int OpcMaxStringLength { get; set; } = 1024 * 1024;
|
||||
|
||||
public static int OpcOperationTimeout { get; set; } = 120000;
|
||||
|
||||
public static bool TrustMyself { get; set; } = true;
|
||||
|
||||
public static int OpcStackTraceMask { get; set; } = Utils.TraceMasks.Error | Utils.TraceMasks.Security | Utils.TraceMasks.StackTrace | Utils.TraceMasks.StartStop;
|
||||
|
||||
public static bool OpcPublisherAutoTrustServerCerts { get; set; } = false;
|
||||
|
||||
public static uint OpcSessionCreationTimeout { get; set; } = 10;
|
||||
|
||||
public static uint OpcSessionCreationBackoffMax { get; set; } = 5;
|
||||
|
||||
public static uint OpcKeepAliveDisconnectThreshold { get; set; } = 5;
|
||||
|
||||
public static int OpcKeepAliveIntervalInSec { get; set; } = 2;
|
||||
|
||||
|
||||
public const int OpcSamplingIntervalDefault = 1000;
|
||||
|
||||
public static int OpcSamplingInterval { get; set; } = OpcSamplingIntervalDefault;
|
||||
|
||||
|
||||
public const int OpcPublishingIntervalDefault = 0;
|
||||
|
||||
public static int OpcPublishingInterval { get; set; } = OpcPublishingIntervalDefault;
|
||||
|
||||
public static string PublisherServerSecurityPolicy { get; set; } = SecurityPolicies.Basic128Rsa15;
|
||||
|
||||
public static string OpcOwnCertStoreType { get; set; } = Directory;
|
||||
|
||||
|
||||
public static string OpcOwnCertDirectoryStorePathDefault => "CertificateStores/own";
|
||||
|
||||
public static string OpcOwnCertX509StorePathDefault => "CurrentUser\\UA_MachineDefault";
|
||||
|
||||
public static string OpcOwnCertStorePath { get; set; } = OpcOwnCertDirectoryStorePathDefault;
|
||||
|
||||
public static string OpcTrustedCertStoreType { get; set; } = Directory;
|
||||
|
||||
public static string OpcTrustedCertDirectoryStorePathDefault => "CertificateStores/trusted";
|
||||
|
||||
public static string OpcTrustedCertX509StorePathDefault => "CurrentUser\\UA_MachineDefault";
|
||||
|
||||
public static string OpcTrustedCertStorePath { get; set; } = null;
|
||||
|
||||
public static string OpcRejectedCertStoreType { get; set; } = Directory;
|
||||
|
||||
public static string OpcRejectedCertDirectoryStorePathDefault => "CertificateStores/rejected";
|
||||
|
||||
public static string OpcRejectedCertX509StorePathDefault => "CurrentUser\\UA_MachineDefault";
|
||||
|
||||
public static string OpcRejectedCertStorePath { get; set; } = OpcRejectedCertDirectoryStorePathDefault;
|
||||
|
||||
public static string OpcIssuerCertStoreType { get; set; } = Directory;
|
||||
|
||||
|
||||
public static string OpcIssuerCertDirectoryStorePathDefault => "CertificateStores/issuers";
|
||||
|
||||
public static string OpcIssuerCertX509StorePathDefault => "CurrentUser\\UA_MachineDefault";
|
||||
|
||||
public static string OpcIssuerCertStorePath { get; set; } = OpcIssuerCertDirectoryStorePathDefault;
|
||||
|
||||
public static int LdsRegistrationInterval { get; set; } = 0;
|
||||
|
||||
public static int OpcTraceToLoggerVerbose { get; set; } = 0;
|
||||
public static int OpcTraceToLoggerDebug { get; set; } = 0;
|
||||
public static int OpcTraceToLoggerInformation { get; set; } = 0;
|
||||
public static int OpcTraceToLoggerWarning { get; set; } = 0;
|
||||
public static int OpcTraceToLoggerError { get; set; } = 0;
|
||||
public static int OpcTraceToLoggerFatal { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Configures all OPC stack settings
|
||||
/// </summary>
|
||||
public async Task ConfigureAsync()
|
||||
{
|
||||
// Instead of using a Config.xml we configure everything programmatically.
|
||||
|
||||
//
|
||||
// OPC UA Application configuration
|
||||
//
|
||||
PublisherOpcApplicationConfiguration = new ApplicationConfiguration();
|
||||
|
||||
// Passed in as command line argument
|
||||
PublisherOpcApplicationConfiguration.ApplicationName = ApplicationName;
|
||||
PublisherOpcApplicationConfiguration.ApplicationUri = $"urn:{Utils.GetHostName()}:{PublisherOpcApplicationConfiguration.ApplicationName}:microsoft:";
|
||||
PublisherOpcApplicationConfiguration.ProductUri = "https://github.com/Azure/iot-edge-opc-publisher";
|
||||
PublisherOpcApplicationConfiguration.ApplicationType = ApplicationType.ClientAndServer;
|
||||
|
||||
|
||||
//
|
||||
// Security configuration
|
||||
//
|
||||
PublisherOpcApplicationConfiguration.SecurityConfiguration = new SecurityConfiguration();
|
||||
|
||||
// Application certificate
|
||||
PublisherOpcApplicationConfiguration.SecurityConfiguration.ApplicationCertificate = new CertificateIdentifier();
|
||||
PublisherOpcApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.StoreType = OpcOwnCertStoreType;
|
||||
PublisherOpcApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.StorePath = OpcOwnCertStorePath;
|
||||
PublisherOpcApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.SubjectName = PublisherOpcApplicationConfiguration.ApplicationName;
|
||||
Logger.Information($"Application Certificate store type is: {PublisherOpcApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.StoreType}");
|
||||
Logger.Information($"Application Certificate store path is: {PublisherOpcApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.StorePath}");
|
||||
Logger.Information($"Application Certificate subject name is: {PublisherOpcApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.SubjectName}");
|
||||
|
||||
// Use existing certificate, if it is there.
|
||||
X509Certificate2 certificate = await PublisherOpcApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.Find(true);
|
||||
if (certificate == null)
|
||||
{
|
||||
Logger.Information($"No existing Application certificate found. Create a self-signed Application certificate valid from yesterday for {CertificateFactory.defaultLifeTime} months,");
|
||||
Logger.Information($"with a {CertificateFactory.defaultKeySize} bit key and {CertificateFactory.defaultHashSize} bit hash.");
|
||||
certificate = CertificateFactory.CreateCertificate(
|
||||
PublisherOpcApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.StoreType,
|
||||
PublisherOpcApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.StorePath,
|
||||
null,
|
||||
PublisherOpcApplicationConfiguration.ApplicationUri,
|
||||
PublisherOpcApplicationConfiguration.ApplicationName,
|
||||
PublisherOpcApplicationConfiguration.ApplicationName,
|
||||
null,
|
||||
CertificateFactory.defaultKeySize,
|
||||
DateTime.UtcNow - TimeSpan.FromDays(1),
|
||||
CertificateFactory.defaultLifeTime,
|
||||
CertificateFactory.defaultHashSize,
|
||||
false,
|
||||
null,
|
||||
null
|
||||
);
|
||||
PublisherOpcApplicationConfiguration.SecurityConfiguration.ApplicationCertificate.Certificate = certificate ?? throw new Exception("OPC UA application certificate can not be created! Cannot continue without it!");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Information("Application certificate found in Application Certificate Store");
|
||||
}
|
||||
PublisherOpcApplicationConfiguration.ApplicationUri = Utils.GetApplicationUriFromCertificate(certificate);
|
||||
Logger.Information($"Application certificate is for Application URI '{PublisherOpcApplicationConfiguration.ApplicationUri}', Application '{PublisherOpcApplicationConfiguration.ApplicationName} and has Subject '{PublisherOpcApplicationConfiguration.ApplicationName}'");
|
||||
|
||||
// TrustedIssuerCertificates
|
||||
PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedIssuerCertificates = new CertificateTrustList();
|
||||
PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedIssuerCertificates.StoreType = OpcIssuerCertStoreType;
|
||||
PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedIssuerCertificates.StorePath = OpcIssuerCertStorePath;
|
||||
Logger.Information($"Trusted Issuer store type is: {PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedIssuerCertificates.StoreType}");
|
||||
Logger.Information($"Trusted Issuer Certificate store path is: {PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedIssuerCertificates.StorePath}");
|
||||
|
||||
// TrustedPeerCertificates
|
||||
PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates = new CertificateTrustList();
|
||||
PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.StoreType = OpcTrustedCertStoreType;
|
||||
if (string.IsNullOrEmpty(OpcTrustedCertStorePath))
|
||||
{
|
||||
// Set default.
|
||||
PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.StorePath = OpcTrustedCertStoreType == X509Store ? OpcTrustedCertX509StorePathDefault : OpcTrustedCertDirectoryStorePathDefault;
|
||||
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_TPC_SP")))
|
||||
{
|
||||
// Use environment variable.
|
||||
PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.StorePath = Environment.GetEnvironmentVariable("_TPC_SP");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.StorePath = OpcTrustedCertStorePath;
|
||||
}
|
||||
Logger.Information($"Trusted Peer Certificate store type is: {PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.StoreType}");
|
||||
Logger.Information($"Trusted Peer Certificate store path is: {PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.StorePath}");
|
||||
|
||||
// RejectedCertificateStore
|
||||
PublisherOpcApplicationConfiguration.SecurityConfiguration.RejectedCertificateStore = new CertificateTrustList();
|
||||
PublisherOpcApplicationConfiguration.SecurityConfiguration.RejectedCertificateStore.StoreType = OpcRejectedCertStoreType;
|
||||
PublisherOpcApplicationConfiguration.SecurityConfiguration.RejectedCertificateStore.StorePath = OpcRejectedCertStorePath;
|
||||
Logger.Information($"Rejected certificate store type is: {PublisherOpcApplicationConfiguration.SecurityConfiguration.RejectedCertificateStore.StoreType}");
|
||||
Logger.Information($"Rejected Certificate store path is: {PublisherOpcApplicationConfiguration.SecurityConfiguration.RejectedCertificateStore.StorePath}");
|
||||
|
||||
// AutoAcceptUntrustedCertificates
|
||||
// This is a security risk and should be set to true only for debugging purposes.
|
||||
PublisherOpcApplicationConfiguration.SecurityConfiguration.AutoAcceptUntrustedCertificates = false;
|
||||
|
||||
// RejectSHA1SignedCertificates
|
||||
// We allow SHA1 certificates for now as many OPC Servers still use them
|
||||
PublisherOpcApplicationConfiguration.SecurityConfiguration.RejectSHA1SignedCertificates = false;
|
||||
Logger.Information($"Rejection of SHA1 signed certificates is {(PublisherOpcApplicationConfiguration.SecurityConfiguration.RejectSHA1SignedCertificates ? "enabled" : "disabled")}");
|
||||
|
||||
// MinimunCertificatesKeySize
|
||||
// We allow a minimum key size of 1024 bit, as many OPC UA servers still use them
|
||||
PublisherOpcApplicationConfiguration.SecurityConfiguration.MinimumCertificateKeySize = 1024;
|
||||
Logger.Information($"Minimum certificate key size set to {PublisherOpcApplicationConfiguration.SecurityConfiguration.MinimumCertificateKeySize}");
|
||||
|
||||
// We make the default reference stack behavior configurable to put our own certificate into the trusted peer store.
|
||||
if (TrustMyself)
|
||||
{
|
||||
// Ensure it is trusted
|
||||
try
|
||||
{
|
||||
ICertificateStore store = PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.OpenStore();
|
||||
if (store == null)
|
||||
{
|
||||
Logger.Information($"Can not open trusted peer store. StorePath={PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.StorePath}");
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.Information($"Adding publisher certificate to trusted peer store. StorePath={PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.StorePath}");
|
||||
X509Certificate2 publicKey = new X509Certificate2(certificate.RawData);
|
||||
X509Certificate2Collection certCollection = await store.FindByThumbprint(publicKey.Thumbprint);
|
||||
if (certCollection.Count > 0)
|
||||
{
|
||||
Logger.Information($"A certificate with the same thumbprint is already in the trusted store.");
|
||||
}
|
||||
else
|
||||
{
|
||||
await store.Add(publicKey);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
store.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e, $"Can not add publisher certificate to trusted peer store. StorePath={PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.StorePath}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Information("Publisher certificate is not added to trusted peer store.");
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// TransportConfigurations
|
||||
//
|
||||
|
||||
PublisherOpcApplicationConfiguration.TransportQuotas = new TransportQuotas();
|
||||
PublisherOpcApplicationConfiguration.TransportQuotas.MaxByteStringLength = 4 * 1024 * 1024;
|
||||
PublisherOpcApplicationConfiguration.TransportQuotas.MaxMessageSize = 4 * 1024 * 1024;
|
||||
|
||||
// the maximum string length could be set to ajust for large number of nodes when reading the list of published nodes
|
||||
PublisherOpcApplicationConfiguration.TransportQuotas.MaxStringLength = OpcMaxStringLength;
|
||||
|
||||
// the OperationTimeout should be twice the minimum value for PublishingInterval * KeepAliveCount, so set to 120s
|
||||
PublisherOpcApplicationConfiguration.TransportQuotas.OperationTimeout = OpcOperationTimeout;
|
||||
Logger.Information($"OperationTimeout set to {PublisherOpcApplicationConfiguration.TransportQuotas.OperationTimeout}");
|
||||
|
||||
|
||||
//
|
||||
// ServerConfiguration
|
||||
//
|
||||
PublisherOpcApplicationConfiguration.ServerConfiguration = new ServerConfiguration();
|
||||
|
||||
// BaseAddresses
|
||||
if (PublisherOpcApplicationConfiguration.ServerConfiguration.BaseAddresses.Count == 0)
|
||||
{
|
||||
// We do not use the localhost replacement mechanism of the configuration loading, to immediately show the base address here
|
||||
PublisherOpcApplicationConfiguration.ServerConfiguration.BaseAddresses.Add($"opc.tcp://{Utils.GetHostName()}:{PublisherServerPort}{PublisherServerPath}");
|
||||
}
|
||||
foreach (var endpoint in PublisherOpcApplicationConfiguration.ServerConfiguration.BaseAddresses)
|
||||
{
|
||||
Logger.Information($"Publisher server base address: {endpoint}");
|
||||
}
|
||||
|
||||
// SecurityPolicies
|
||||
// We do not allow security policy SecurityPolicies.None, but always high security
|
||||
ServerSecurityPolicy newPolicy = new ServerSecurityPolicy()
|
||||
{
|
||||
SecurityMode = MessageSecurityMode.SignAndEncrypt,
|
||||
SecurityPolicyUri = SecurityPolicies.Basic256Sha256
|
||||
};
|
||||
PublisherOpcApplicationConfiguration.ServerConfiguration.SecurityPolicies.Add(newPolicy);
|
||||
Logger.Information($"Security policy {newPolicy.SecurityPolicyUri} with mode {newPolicy.SecurityMode} added");
|
||||
|
||||
// MaxRegistrationInterval
|
||||
PublisherOpcApplicationConfiguration.ServerConfiguration.MaxRegistrationInterval = LdsRegistrationInterval;
|
||||
Logger.Information($"LDS(-ME) registration intervall set to {LdsRegistrationInterval} ms (0 means no registration)");
|
||||
|
||||
//
|
||||
// TraceConfiguration
|
||||
//
|
||||
//
|
||||
// TraceConfiguration
|
||||
//
|
||||
PublisherOpcApplicationConfiguration.TraceConfiguration = new TraceConfiguration();
|
||||
PublisherOpcApplicationConfiguration.TraceConfiguration.TraceMasks = OpcStackTraceMask;
|
||||
PublisherOpcApplicationConfiguration.TraceConfiguration.ApplySettings();
|
||||
Utils.Tracing.TraceEventHandler += new EventHandler<TraceEventArgs>(LoggerOpcUaTraceHandler);
|
||||
Logger.Information($"opcstacktracemask set to: 0x{OpcStackTraceMask:X}");
|
||||
|
||||
// add default client configuration
|
||||
PublisherOpcApplicationConfiguration.ClientConfiguration = new ClientConfiguration();
|
||||
|
||||
// validate the configuration now
|
||||
await PublisherOpcApplicationConfiguration.Validate(PublisherOpcApplicationConfiguration.ApplicationType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event handler to log OPC UA stack trace messages into own logger.
|
||||
/// </summary>
|
||||
private static void LoggerOpcUaTraceHandler(object sender, TraceEventArgs e)
|
||||
{
|
||||
// return fast if no trace needed
|
||||
if ((e.TraceMask & OpcStackTraceMask) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// e.Exception and e.Message are always null
|
||||
|
||||
// format the trace message
|
||||
string message = string.Empty;
|
||||
message = string.Format(e.Format, e.Arguments)?.Trim();
|
||||
message = "OPC: " + message;
|
||||
|
||||
// map logging level
|
||||
if ((e.TraceMask & OpcTraceToLoggerVerbose) != 0)
|
||||
{
|
||||
Logger.Verbose(message);
|
||||
return;
|
||||
}
|
||||
if ((e.TraceMask & OpcTraceToLoggerDebug) != 0)
|
||||
{
|
||||
Logger.Debug(message);
|
||||
return;
|
||||
}
|
||||
if ((e.TraceMask & OpcTraceToLoggerInformation) != 0)
|
||||
{
|
||||
Logger.Information(message);
|
||||
return;
|
||||
}
|
||||
if ((e.TraceMask & OpcTraceToLoggerWarning) != 0)
|
||||
{
|
||||
Logger.Warning(message);
|
||||
return;
|
||||
}
|
||||
if ((e.TraceMask & OpcTraceToLoggerError) != 0)
|
||||
{
|
||||
Logger.Error(message);
|
||||
return;
|
||||
}
|
||||
if ((e.TraceMask & OpcTraceToLoggerFatal) != 0)
|
||||
{
|
||||
Logger.Fatal(message);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,21 +21,37 @@ namespace OpcPublisher
|
|||
using static IotEdgeHubCommunication;
|
||||
using static Opc.Ua.CertificateStoreType;
|
||||
using static OpcSession;
|
||||
using static OpcStackConfiguration;
|
||||
using static OpcApplicationConfiguration;
|
||||
using static PublisherNodeConfiguration;
|
||||
using static PublisherTelemetryConfiguration;
|
||||
using static System.Console;
|
||||
|
||||
public class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// IoTHub/EdgeHub communication objects.
|
||||
/// </summary>
|
||||
public static IotHubCommunication IotHubCommunication;
|
||||
public static IotEdgeHubCommunication IotEdgeHubCommunication;
|
||||
|
||||
/// <summary>
|
||||
/// Shutdown token source.
|
||||
/// </summary>
|
||||
public static CancellationTokenSource ShutdownTokenSource;
|
||||
|
||||
/// <summary>
|
||||
/// Used as delay in sec when shutting down the application.
|
||||
/// </summary>
|
||||
public static uint PublisherShutdownWaitPeriod { get; } = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Stores startup time.
|
||||
/// </summary>
|
||||
public static DateTime PublisherStartTime { get; set; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>
|
||||
/// Logging object.
|
||||
/// </summary>
|
||||
public static Serilog.Core.Logger Logger { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
|
@ -91,19 +107,6 @@ namespace OpcPublisher
|
|||
}
|
||||
}
|
||||
},
|
||||
{ "sd|shopfloordomain=", $"same as site option, only there for backward compatibility\n" +
|
||||
"The value must follow the syntactical rules of a DNS hostname.\nDefault: not set", (string s) => {
|
||||
Regex siteNameRegex = new Regex("^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$");
|
||||
if (siteNameRegex.IsMatch(s))
|
||||
{
|
||||
PublisherSite = s;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new OptionException("The shopfloor domain is not a valid DNS hostname.", "shopfloordomain");
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "ic|iotcentral", $"publisher will send OPC UA data in IoTCentral compatible format (DisplayName of a node is used as key, this key is the Field name in IoTCentral). you need to ensure that all DisplayName's are unique. (Auto enables fetch display name)\nDefault: {IotCentralMode}", b => IotCentralMode = FetchOpcNodeDisplayName = b != null },
|
||||
{ "sw|sessionconnectwait=", $"specify the wait time in seconds publisher is trying to connect to disconnected endpoints and starts monitoring unmonitored items\nMin: 10\nDefault: {SessionConnectWaitSec}", (int i) => {
|
||||
if (i > 10)
|
||||
|
@ -129,8 +132,6 @@ namespace OpcPublisher
|
|||
},
|
||||
{ "di|diagnosticsinterval=", $"shows publisher diagnostic info at the specified interval in seconds (need log level info). 0 disables diagnostic output.\nDefault: {DiagnosticsInterval}", (uint u) => DiagnosticsInterval = u },
|
||||
|
||||
{ "vc|verboseconsole=", $"ignored, only supported for backward comaptibility.", b => {}},
|
||||
|
||||
{ "ns|noshutdown=", $"same as runforever.\nDefault: {_noShutdown}", (bool b) => _noShutdown = b },
|
||||
{ "rf|runforever", $"publisher can not be stopped by pressing a key on the console, but will run forever.\nDefault: {_noShutdown}", b => _noShutdown = b != null },
|
||||
|
||||
|
@ -159,7 +160,7 @@ namespace OpcPublisher
|
|||
}
|
||||
},
|
||||
// IoTHub specific options
|
||||
{ "ih|iothubprotocol=", $"{(IsIotEdgeModule ? "not supported when running as IoTEdge module (Mqtt_Tcp_Only is enforced)\n" : $"the protocol to use for communication with Azure IoTHub (allowed values: {string.Join(", ", Enum.GetNames(IotHubProtocol.GetType()))}).\nDefault: {Enum.GetName(IotHubProtocol.GetType(), IotHubProtocol)}")}",
|
||||
{ "ih|iothubprotocol=", $"{(IsIotEdgeModule ? "not supported when running as IoT Edge module\n" : $"the protocol to use for communication with Azure IoTHub (allowed values: {string.Join(", ", Enum.GetNames(IotHubProtocol.GetType()))}).\nDefault: {Enum.GetName(IotHubProtocol.GetType(), IotHubProtocol)}")}",
|
||||
(Microsoft.Azure.Devices.Client.TransportType p) => {
|
||||
if (IsIotEdgeModule)
|
||||
{
|
||||
|
@ -205,8 +206,8 @@ namespace OpcPublisher
|
|||
},
|
||||
|
||||
// opc server configuration options
|
||||
{ "pn|portnum=", $"the server port of the publisher OPC server endpoint.\nDefault: {PublisherServerPort}", (ushort p) => PublisherServerPort = p },
|
||||
{ "pa|path=", $"the enpoint URL path part of the publisher OPC server endpoint.\nDefault: '{PublisherServerPath}'", (string a) => PublisherServerPath = a },
|
||||
{ "pn|portnum=", $"the server port of the publisher OPC server endpoint.\nDefault: {ServerPort}", (ushort p) => ServerPort = p },
|
||||
{ "pa|path=", $"the enpoint URL path part of the publisher OPC server endpoint.\nDefault: '{ServerPath}'", (string a) => ServerPath = a },
|
||||
{ "lr|ldsreginterval=", $"the LDS(-ME) registration interval in ms. If 0, then the registration is disabled.\nDefault: {LdsRegistrationInterval}", (int i) => {
|
||||
if (i >= 0)
|
||||
{
|
||||
|
@ -299,9 +300,7 @@ namespace OpcPublisher
|
|||
}
|
||||
}
|
||||
},
|
||||
{ "st|opcstacktracemask=", $"ignored, only supported for backward comaptibility.", i => {}},
|
||||
|
||||
{ "as|autotrustservercerts=", $"same as autoaccept, only supported for backward cmpatibility.\nDefault: {OpcPublisherAutoTrustServerCerts}", (bool b) => OpcPublisherAutoTrustServerCerts = b },
|
||||
{ "aa|autoaccept", $"the publisher trusts all servers it is establishing a connection to.\nDefault: {OpcPublisherAutoTrustServerCerts}", b => OpcPublisherAutoTrustServerCerts = b != null },
|
||||
|
||||
// trust own public cert option
|
||||
|
@ -311,7 +310,7 @@ namespace OpcPublisher
|
|||
{ "fd|fetchdisplayname=", $"same as fetchname.\nDefault: {FetchOpcNodeDisplayName}", (bool b) => FetchOpcNodeDisplayName = IotCentralMode ? true : b },
|
||||
{ "fn|fetchname", $"enable to read the display name of a published node from the server. this will increase the runtime.\nDefault: {FetchOpcNodeDisplayName}", b => FetchOpcNodeDisplayName = IotCentralMode ? true : b != null },
|
||||
|
||||
// own cert store options
|
||||
// cert store options
|
||||
{ "at|appcertstoretype=", $"the own application cert store type. \n(allowed values: Directory, X509Store)\nDefault: '{OpcOwnCertStoreType}'", (string s) => {
|
||||
if (s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) || s.Equals(CertificateStoreType.Directory, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
@ -329,60 +328,100 @@ namespace OpcPublisher
|
|||
$"Directory: '{OpcOwnCertDirectoryStorePathDefault}'", (string s) => OpcOwnCertStorePath = s
|
||||
},
|
||||
|
||||
// trusted cert store options
|
||||
{
|
||||
"tt|trustedcertstoretype=", $"the trusted cert store type. \n(allowed values: Directory, X509Store)\nDefault: {OpcTrustedCertStoreType}", (string s) => {
|
||||
if (s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) || s.Equals(CertificateStoreType.Directory, StringComparison.OrdinalIgnoreCase))
|
||||
{ "tp|trustedcertstorepath=", $"the path of the trusted cert store\nDefault: '{OpcTrustedCertDirectoryStorePathDefault}'", (string s) => OpcTrustedCertStorePath = s },
|
||||
|
||||
{ "rp|rejectedcertstorepath=", $"the path of the rejected cert store\nDefault '{OpcRejectedCertDirectoryStorePathDefault}'", (string s) => OpcRejectedCertStorePath = s },
|
||||
|
||||
{ "ip|issuercertstorepath=", $"the path of the trusted issuer cert store\nDefault '{OpcIssuerCertDirectoryStorePathDefault}'", (string s) => OpcIssuerCertStorePath = s },
|
||||
|
||||
{ "csr", $"show data to create a certificate signing request\nDefault '{ShowCreateSigningRequestInfo}'", c => ShowCreateSigningRequestInfo = c != null },
|
||||
|
||||
{ "ab|applicationcertbase64=", $"update/set this applications certificate with the certificate passed in as bas64 string", (string s) =>
|
||||
{
|
||||
NewCertificateBase64String = s;
|
||||
}
|
||||
},
|
||||
{ "af|applicationcertfile=", $"update/set this applications certificate with the certificate file specified", (string s) =>
|
||||
{
|
||||
if (File.Exists(s))
|
||||
{
|
||||
OpcTrustedCertStoreType = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? X509Store : CertificateStoreType.Directory;
|
||||
OpcTrustedCertStorePath = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? OpcTrustedCertX509StorePathDefault : OpcTrustedCertDirectoryStorePathDefault;
|
||||
NewCertificateFileName = s;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new OptionException();
|
||||
throw new OptionException("The file '{s}' does not exist.", "applicationcertfile");
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "tp|trustedcertstorepath=", $"the path of the trusted cert store\nDefault (depends on store type):\n" +
|
||||
$"X509Store: '{OpcTrustedCertX509StorePathDefault}'\n" +
|
||||
$"Directory: '{OpcTrustedCertDirectoryStorePathDefault}'", (string s) => OpcTrustedCertStorePath = s
|
||||
},
|
||||
|
||||
// rejected cert store options
|
||||
{ "rt|rejectedcertstoretype=", $"the rejected cert store type. \n(allowed values: Directory, X509Store)\nDefault: {OpcRejectedCertStoreType}", (string s) => {
|
||||
if (s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) || s.Equals(CertificateStoreType.Directory, StringComparison.OrdinalIgnoreCase))
|
||||
{ "pb|privatekeybase64=", $"initial provisioning of the application certificate (with a PEM or PFX fomat) requires a private key passed in as base64 string", (string s) =>
|
||||
{
|
||||
PrivateKeyBase64String = s;
|
||||
}
|
||||
},
|
||||
{ "pk|privatekeyfile=", $"initial provisioning of the application certificate (with a PEM or PFX fomat) requires a private key passed in as file", (string s) =>
|
||||
{
|
||||
if (File.Exists(s))
|
||||
{
|
||||
OpcRejectedCertStoreType = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? X509Store : CertificateStoreType.Directory;
|
||||
OpcRejectedCertStorePath = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? OpcRejectedCertX509StorePathDefault : OpcRejectedCertDirectoryStorePathDefault;
|
||||
PrivateKeyFileName = s;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new OptionException();
|
||||
throw new OptionException("The file '{s}' does not exist.", "privatekeyfile");
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "rp|rejectedcertstorepath=", $"the path of the rejected cert store\nDefault (depends on store type):\n" +
|
||||
$"X509Store: '{OpcRejectedCertX509StorePathDefault}'\n" +
|
||||
$"Directory: '{OpcRejectedCertDirectoryStorePathDefault}'", (string s) => OpcRejectedCertStorePath = s
|
||||
},
|
||||
|
||||
// issuer cert store options
|
||||
{
|
||||
"it|issuercertstoretype=", $"the trusted issuer cert store type. \n(allowed values: Directory, X509Store)\nDefault: {OpcIssuerCertStoreType}", (string s) => {
|
||||
if (s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) || s.Equals(CertificateStoreType.Directory, StringComparison.OrdinalIgnoreCase))
|
||||
{ "cp|certpassword=", $"the optional password for the PEM or PFX or the installed application certificate", (string s) =>
|
||||
{
|
||||
CertificatePassword = s;
|
||||
}
|
||||
},
|
||||
|
||||
{ "tb|addtrustedcertbase64=", $"adds the certificate to the applications trusted cert store passed in as base64 string (multiple strings supported)", (string s) =>
|
||||
{
|
||||
TrustedCertificateBase64Strings = ParseListOfStrings(s);
|
||||
}
|
||||
},
|
||||
{ "tf|addtrustedcertfile=", $"adds the certificate file(s) to the applications trusted cert store passed in as base64 string (multiple filenames supported)", (string s) =>
|
||||
{
|
||||
TrustedCertificateFileNames = ParseListOfFileNames(s, "addtrustedcertfile");
|
||||
}
|
||||
},
|
||||
|
||||
{ "ib|addissuercertbase64=", $"adds the specified issuer certificate to the applications trusted issuer cert store passed in as base64 string (multiple strings supported)", (string s) =>
|
||||
{
|
||||
IssuerCertificateBase64Strings = ParseListOfStrings(s);
|
||||
}
|
||||
},
|
||||
{ "if|addissuercertfile=", $"adds the specified issuer certificate file(s) to the applications trusted issuer cert store (multiple filenames supported)", (string s) =>
|
||||
{
|
||||
IssuerCertificateFileNames = ParseListOfFileNames(s, "addissuercertfile");
|
||||
}
|
||||
},
|
||||
|
||||
{ "rb|updatecrlbase64=", $"update the CRL passed in as base64 string to the corresponding cert store (trusted or trusted issuer)", (string s) =>
|
||||
{
|
||||
CrlBase64String = s;
|
||||
}
|
||||
},
|
||||
{ "uc|updatecrlfile=", $"update the CRL passed in as file to the corresponding cert store (trusted or trusted issuer)", (string s) =>
|
||||
{
|
||||
if (File.Exists(s))
|
||||
{
|
||||
OpcIssuerCertStoreType = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? X509Store : CertificateStoreType.Directory;
|
||||
OpcIssuerCertStorePath = s.Equals(X509Store, StringComparison.OrdinalIgnoreCase) ? OpcIssuerCertX509StorePathDefault : OpcIssuerCertDirectoryStorePathDefault;
|
||||
CrlFileName = s;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new OptionException();
|
||||
throw new OptionException("The file '{s}' does not exist.", "updatecrlfile");
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "ip|issuercertstorepath=", $"the path of the trusted issuer cert store\nDefault (depends on store type):\n" +
|
||||
$"X509Store: '{OpcIssuerCertX509StorePathDefault}'\n" +
|
||||
$"Directory: '{OpcIssuerCertDirectoryStorePathDefault}'", (string s) => OpcIssuerCertStorePath = s
|
||||
|
||||
{ "rc|removecert=", $"remove cert(s) with the given thumbprint(s) (multiple thumbprints supported)", (string s) =>
|
||||
{
|
||||
ThumbprintsToRemove = ParseListOfStrings(s);
|
||||
}
|
||||
},
|
||||
|
||||
// device connection string cert store options
|
||||
|
@ -406,6 +445,28 @@ namespace OpcPublisher
|
|||
// misc
|
||||
{ "i|install", $"register OPC Publisher with IoTHub and then exits.\nDefault: {_installOnly}", i => _installOnly = i != null },
|
||||
{ "h|help", "show this message and exit", h => shouldShowHelp = h != null },
|
||||
|
||||
// all the following are only supported to not break existing command lines, but some of them are just ignored
|
||||
{ "st|opcstacktracemask=", $"ignored, only supported for backward comaptibility.", i => {}},
|
||||
{ "sd|shopfloordomain=", $"same as site option, only there for backward compatibility\n" +
|
||||
"The value must follow the syntactical rules of a DNS hostname.\nDefault: not set", (string s) => {
|
||||
Regex siteNameRegex = new Regex("^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$");
|
||||
if (siteNameRegex.IsMatch(s))
|
||||
{
|
||||
PublisherSite = s;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new OptionException("The shopfloor domain is not a valid DNS hostname.", "shopfloordomain");
|
||||
}
|
||||
}
|
||||
},
|
||||
{ "vc|verboseconsole=", $"ignored, only supported for backward comaptibility.", b => {}},
|
||||
{ "as|autotrustservercerts=", $"same as autoaccept, only supported for backward cmpatibility.\nDefault: {OpcPublisherAutoTrustServerCerts}", (bool b) => OpcPublisherAutoTrustServerCerts = b },
|
||||
{ "tt|trustedcertstoretype=", $"ignored, only supported for backward compatibility. the trusted cert store will always reside in a directory.", s => { }},
|
||||
{ "rt|rejectedcertstoretype=", $"ignored, only supported for backward compatibility. the rejected cert store will always reside in a directory.", s => { }},
|
||||
{ "it|issuercertstoretype=", $"ignored, only supported for backward compatibility. the trusted issuer cert store will always reside in a directory.", s => { }},
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
@ -508,8 +569,8 @@ namespace OpcPublisher
|
|||
}
|
||||
|
||||
// init OPC configuration and tracing
|
||||
OpcStackConfiguration opcStackConfiguration = new OpcStackConfiguration();
|
||||
await opcStackConfiguration.ConfigureAsync();
|
||||
OpcApplicationConfiguration opcApplicationConfiguration = new OpcApplicationConfiguration();
|
||||
await opcApplicationConfiguration.ConfigureAsync();
|
||||
|
||||
// log shopfloor site setting
|
||||
if (string.IsNullOrEmpty(PublisherSite))
|
||||
|
@ -525,20 +586,20 @@ namespace OpcPublisher
|
|||
if (OpcPublisherAutoTrustServerCerts)
|
||||
{
|
||||
Logger.Information("Publisher configured to auto trust server certificates of the servers it is connecting to.");
|
||||
PublisherOpcApplicationConfiguration.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(CertificateValidator_AutoTrustServerCerts);
|
||||
OpcApplicationConfiguration.ApplicationConfiguration.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(CertificateValidator_AutoTrustServerCerts);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Information("Publisher configured to not auto trust server certificates. When connecting to servers, you need to manually copy the rejected server certs to the trusted store to trust them.");
|
||||
PublisherOpcApplicationConfiguration.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(CertificateValidator_Default);
|
||||
OpcApplicationConfiguration.ApplicationConfiguration.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(CertificateValidator_Default);
|
||||
}
|
||||
|
||||
// start our server interface
|
||||
try
|
||||
{
|
||||
Logger.Information($"Starting server on endpoint {PublisherOpcApplicationConfiguration.ServerConfiguration.BaseAddresses[0].ToString()} ...");
|
||||
Logger.Information($"Starting server on endpoint {OpcApplicationConfiguration.ApplicationConfiguration.ServerConfiguration.BaseAddresses[0].ToString()} ...");
|
||||
_publisherServer = new PublisherServer();
|
||||
_publisherServer.Start(PublisherOpcApplicationConfiguration);
|
||||
_publisherServer.Start(OpcApplicationConfiguration.ApplicationConfiguration);
|
||||
Logger.Information("Server started.");
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@ -767,9 +828,9 @@ namespace OpcPublisher
|
|||
{
|
||||
Logger.Information($"OPC Publisher does not trust the server with the certificate subject '{e.Certificate.Subject}'.");
|
||||
Logger.Information("If you want to trust this certificate, please copy it from the directory:");
|
||||
Logger.Information($"{PublisherOpcApplicationConfiguration.SecurityConfiguration.RejectedCertificateStore.StorePath}/certs");
|
||||
Logger.Information($"{OpcApplicationConfiguration.ApplicationConfiguration.SecurityConfiguration.RejectedCertificateStore.StorePath}/certs");
|
||||
Logger.Information("to the directory:");
|
||||
Logger.Information($"{PublisherOpcApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.StorePath}/certs");
|
||||
Logger.Information($"{OpcApplicationConfiguration.ApplicationConfiguration.SecurityConfiguration.TrustedPeerCertificates.StorePath}/certs");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -871,6 +932,94 @@ namespace OpcPublisher
|
|||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to build a list of byte arrays out of a comma separated list of base64 strings (optional in double quotes).
|
||||
/// </summary>
|
||||
private static List<string> ParseListOfStrings(string s)
|
||||
{
|
||||
List<string> strings = new List<string>();
|
||||
if (s[0] == '"' && (s.Count(c => c.Equals('"')) % 2 == 0))
|
||||
{
|
||||
while (s.Contains('"'))
|
||||
{
|
||||
int first = 0;
|
||||
int next = 0;
|
||||
first = s.IndexOf('"', next);
|
||||
next = s.IndexOf('"', ++first);
|
||||
strings.Add(s.Substring(first, next - first));
|
||||
s = s.Substring(++next);
|
||||
}
|
||||
}
|
||||
else if (s.Contains(','))
|
||||
{
|
||||
strings = s.Split(',').ToList();
|
||||
strings.ForEach(st => st.Trim());
|
||||
strings = strings.Select(st => st.Trim()).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
strings.Add(s);
|
||||
}
|
||||
return strings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to build a list of filenames out of a comma separated list of filenames (optional in double quotes).
|
||||
/// </summary>
|
||||
private static List<string> ParseListOfFileNames(string s, string option)
|
||||
{
|
||||
List<string> fileNames = new List<string>();
|
||||
if (s[0] == '"' && (s.Count(c => c.Equals('"')) % 2 == 0))
|
||||
{
|
||||
while (s.Contains('"'))
|
||||
{
|
||||
int first = 0;
|
||||
int next = 0;
|
||||
first = s.IndexOf('"', next);
|
||||
next = s.IndexOf('"', ++first);
|
||||
var fileName = s.Substring(first, next - first);
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
fileNames.Add(fileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new OptionException($"The file '{fileName}' does not exist.", option);
|
||||
}
|
||||
s = s.Substring(++next);
|
||||
}
|
||||
}
|
||||
else if (s.Contains(','))
|
||||
{
|
||||
List<string> parsedFileNames = s.Split(',').ToList();
|
||||
parsedFileNames = parsedFileNames.Select(st => st.Trim()).ToList();
|
||||
foreach (var fileName in parsedFileNames)
|
||||
{
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
fileNames.Add(fileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new OptionException($"The file '{fileName}' does not exist.", option);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (File.Exists(s))
|
||||
{
|
||||
fileNames.Add(s);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new OptionException($"The file '{s}' does not exist.", option);
|
||||
}
|
||||
}
|
||||
return fileNames;
|
||||
}
|
||||
|
||||
private static PublisherServer _publisherServer;
|
||||
private static bool _noShutdown = false;
|
||||
private static bool _installOnly = false;
|
||||
|
|
|
@ -7,13 +7,12 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace OpcPublisher
|
||||
{
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using static OpcApplicationConfiguration;
|
||||
using static OpcMonitoredItem;
|
||||
using static OpcSession;
|
||||
using static OpcStackConfiguration;
|
||||
using static Program;
|
||||
|
||||
public static class PublisherNodeConfiguration
|
||||
|
|
|
@ -9,8 +9,8 @@ namespace OpcPublisher
|
|||
using Newtonsoft.Json;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using static OpcApplicationConfiguration;
|
||||
using static OpcPublisher.Program;
|
||||
using static OpcStackConfiguration;
|
||||
using static PublisherNodeConfiguration;
|
||||
|
||||
public class PublisherNodeManager : CustomNodeManager2
|
||||
|
|
|
@ -130,11 +130,9 @@ namespace OpcPublisher
|
|||
}
|
||||
else
|
||||
{
|
||||
RsaUtils.RSADispose(rsa);
|
||||
throw new CryptographicException("Can not encrypt IoTHub security token using generated public key!");
|
||||
}
|
||||
}
|
||||
RsaUtils.RSADispose(rsa);
|
||||
|
||||
// sign the cert with the private key
|
||||
ISignatureFactory signatureFactory = new Asn1SignatureFactory("SHA256WITHRSA", keys.Private, random);
|
||||
|
@ -240,7 +238,6 @@ namespace OpcPublisher
|
|||
return Encoding.ASCII.GetString(token);
|
||||
}
|
||||
}
|
||||
RsaUtils.RSADispose(rsa);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<PackageReference Include="Microsoft.Azure.Devices.Client" Version="1.18.1" />
|
||||
<PackageReference Include="Mono.Options" Version="5.3.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua" Version="1.4.353.15" />
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua" Version="1.4.354.23" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="2.0.2" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="4.0.0" />
|
||||
|
|
Загрузка…
Ссылка в новой задаче