Skip to content

Commit 438f669

Browse files
Brunelle GrossmannAntoine Huret
authored andcommitted
v0.0.0
0 parents  commit 438f669

File tree

10 files changed

+1331
-0
lines changed

10 files changed

+1331
-0
lines changed

README.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# telemetry_opcua_exporter
2+
3+
## Server Configuration
4+
5+
info to set in execution flag :
6+
```go
7+
bindAddress := flag.String("bindAddress", ":4242", "Address to listen on for web interface.")
8+
configFile = flag.String("configFile", "opcua.yaml", "Path to configuration file.")
9+
endpoint := flag.String("endpoint", "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer", "OPC UA Endpoint URL")
10+
auth := flag.String("auth-mode", "UserName", "Authentication Mode: one of Anonymous, UserName, Certificate")
11+
verbosity := flag.String("verbosity", "info", "Log verbosity (debug/info/warn/error/fatal)")
12+
```
13+
To choose policy and mode :
14+
```go
15+
policy := flag.String("sec-policy", "None", "Security Policy URL or one of None, Basic128Rsa15, Basic256, Basic256Sha256")
16+
mode := flag.String("sec-mode", "auto", "Security Mode: one of None, Sign, SignAndEncrypt")
17+
```
18+
"auto" mode will find the highest security level according to selected endpoint
19+
beware that both policy and mode cannot be set to None
20+
21+
If auth is set to "Certificate" are mandatory :
22+
```go
23+
certfile := flag.String("cert", "cert.crt", "Path to certificate file")
24+
keyfile := flag.String("key", "cert.key", "Path to PEM Private Key file")
25+
```
26+
27+
If auth is set to "UserName" are mandatory :
28+
```go
29+
username := flag.String("user", "admin", "Username to use in auth-mode UserName")
30+
password := flag.String("password", "admin", "Password to use in auth-mode UserName")
31+
```
32+
33+
## Metrics Configuration
34+
35+
metrics configuration should be through yaml file following this pattern :
36+
37+
```yaml
38+
metrics:
39+
- name: Temperature # MANDATORY
40+
help: get metrics for machine temperature # MANDATORY
41+
nodeid: ns=2;i=10853 # MANDATORY and UNIQUE for each metric
42+
labels: # if metrics share the same name they can be distinguis by labels
43+
site: MLK
44+
type: gauge # MANDATORY metric type can be counter, gauge, Float, Double
45+
- name: Temperature
46+
help: get metrics for machine temperature
47+
nodeid: ns=2;i=10852
48+
labels:
49+
site: Paris
50+
type: gauge
51+
```
52+
53+
54+
## Https Routes
55+
### show current metrics
56+
```
57+
curl 127.0.0.1:4242/metrics
58+
```
59+
### show current config
60+
```
61+
curl 127.0.0.1:4242/config
62+
```
63+
### reload config from opcua.yaml file
64+
```
65+
curl 127.0.0.1:4242/reload/config
66+
```
67+
### change config from body's request data
68+
```
69+
curl --request POST \
70+
--url 127.0.1:4242/config/update \
71+
--header 'content-type: x-yaml' \
72+
--data "metrics:
73+
- name: plop
74+
help: plop
75+
nodeid: ns=2;i=10853
76+
labels: {}
77+
type: gauge"
78+
```
79+
Please take note that it's also rewrite opcua.yaml with the input file

client/client.go

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package client
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io/ioutil"
7+
"strings"
8+
"time"
9+
10+
"github.com/skilld-labs/opcua"
11+
"github.com/skilld-labs/opcua/errors"
12+
"github.com/skilld-labs/opcua/ua"
13+
"github.com/skilld-labs/telemetry-opcua-exporter/config"
14+
"github.com/skilld-labs/telemetry-opcua-exporter/log"
15+
)
16+
17+
func NewClientFromServerConfig(cfg config.ServerConfig, logger log.Logger) *opcua.Client {
18+
opts := []opcua.Option{}
19+
authMode := *cfg.Auth
20+
switch authMode {
21+
case "Certificate":
22+
return NewCertificateClient(*cfg.CertFile, *cfg.KeyFile, cfg, logger)
23+
case "UserName":
24+
return NewBasicAuthClient(*cfg.Username, *cfg.Password, cfg, logger)
25+
default:
26+
*cfg.Auth = "Anonymous"
27+
opts = append(opts, opcua.AuthAnonymous())
28+
}
29+
return getClientWithOptions(cfg, opts, logger)
30+
}
31+
32+
func NewCertificateClient(certfile, keyfile string, cfg config.ServerConfig, logger log.Logger) *opcua.Client {
33+
return getClientWithOptions(cfg, certificateOptions(certfile, keyfile, logger), logger)
34+
}
35+
36+
func NewBasicAuthClient(username, password string, cfg config.ServerConfig, logger log.Logger) *opcua.Client {
37+
return getClientWithOptions(cfg, []opcua.Option{opcua.AuthUsername(username, password)}, logger)
38+
}
39+
40+
func getClientWithOptions(cfg config.ServerConfig, opts []opcua.Option, logger log.Logger) *opcua.Client {
41+
clientSecurityOpts, err := clientSecurityOptions(cfg, logger)
42+
if err != nil {
43+
logger.Err("%v", err)
44+
}
45+
opts = append(opts, clientSecurityOpts...)
46+
opts = append(opts, opcua.Lifetime(10*time.Minute), opcua.RequestTimeout(5*time.Second), opcua.AutoReconnect(true))
47+
48+
c := opcua.NewClient(*cfg.Endpoint, opts...)
49+
if err := c.Connect(context.Background()); err != nil {
50+
logger.Err("cannot connect opcua client %v", err)
51+
}
52+
return c
53+
}
54+
55+
func certificateOptions(certfile, keyfile string, logger log.Logger) []opcua.Option {
56+
opts := []opcua.Option{}
57+
if keyfile != "" {
58+
opts = append(opts, opcua.PrivateKeyFile(keyfile))
59+
}
60+
cert, err := ioutil.ReadFile(certfile)
61+
if err != nil {
62+
logger.Err("cannot read certfile : %v", err)
63+
}
64+
if certfile != "" {
65+
opts = append(opts, opcua.Certificate(cert))
66+
}
67+
return opts
68+
}
69+
70+
func clientSecurityOptions(cfg config.ServerConfig, logger log.Logger) ([]opcua.Option, error) {
71+
savePolicy := *cfg.Policy
72+
endpoints, err := opcua.GetEndpoints(*cfg.Endpoint)
73+
if err != nil {
74+
return nil, fmt.Errorf("%v", err)
75+
}
76+
authMode := ua.UserTokenTypeFromString(*cfg.Auth)
77+
opts := []opcua.Option{}
78+
var secPolicy string
79+
switch {
80+
case *cfg.Policy == "auto":
81+
case strings.HasPrefix(*cfg.Policy, ua.SecurityPolicyURIPrefix):
82+
secPolicy = *cfg.Policy
83+
*cfg.Policy = ""
84+
case *cfg.Policy == "None" || *cfg.Policy == "Basic128Rsa15" || *cfg.Policy == "Basic256" || *cfg.Policy == "Basic256Sha256" || *cfg.Policy == "Aes128_Sha256_RsaOaep" || *cfg.Policy == "Aes256_Sha256_RsaPss":
85+
secPolicy = ua.SecurityPolicyURIPrefix + *cfg.Policy
86+
*cfg.Policy = ""
87+
default:
88+
return nil, fmt.Errorf("invalid security.Policy: %s", *cfg.Policy)
89+
}
90+
91+
var secMode ua.MessageSecurityMode
92+
switch strings.ToLower(*cfg.Mode) {
93+
case "auto":
94+
case "none":
95+
secMode = ua.MessageSecurityModeNone
96+
*cfg.Mode = ""
97+
case "sign":
98+
secMode = ua.MessageSecurityModeSign
99+
*cfg.Mode = ""
100+
case "signandencrypt":
101+
secMode = ua.MessageSecurityModeSignAndEncrypt
102+
*cfg.Mode = ""
103+
default:
104+
return nil, fmt.Errorf("invalid security.Mode: %s", *cfg.Mode)
105+
}
106+
107+
// Allow input of only one of sec.Mode,sec.Policy when choosing 'None'
108+
if secMode == ua.MessageSecurityModeNone || secPolicy == ua.SecurityPolicyURINone {
109+
secMode = ua.MessageSecurityModeNone
110+
secPolicy = ua.SecurityPolicyURINone
111+
}
112+
113+
// Find the best endpoint based on our input and server recommendation (highest SecurityMode+SecurityLevel)
114+
var serverEndpoint *ua.EndpointDescription
115+
switch {
116+
case *cfg.Mode == "auto" && *cfg.Policy == "auto": // No user selection, choose best
117+
for _, e := range endpoints {
118+
if serverEndpoint == nil || (e.SecurityMode >= serverEndpoint.SecurityMode && e.SecurityLevel >= serverEndpoint.SecurityLevel) {
119+
serverEndpoint = e
120+
}
121+
}
122+
123+
case *cfg.Mode != "auto" && *cfg.Policy == "auto": // User only cares about.Mode, select highest securitylevel with that.Mode
124+
for _, e := range endpoints {
125+
if e.SecurityMode == secMode && (serverEndpoint == nil || e.SecurityLevel >= serverEndpoint.SecurityLevel) {
126+
serverEndpoint = e
127+
}
128+
}
129+
130+
case *cfg.Mode == "auto" && *cfg.Policy != "auto": // User only cares about.Policy, select highest securitylevel with that.Policy
131+
for _, e := range endpoints {
132+
if e.SecurityPolicyURI == secPolicy && (serverEndpoint == nil || e.SecurityLevel >= serverEndpoint.SecurityLevel) {
133+
serverEndpoint = e
134+
}
135+
}
136+
137+
default: // User cares about both
138+
for _, e := range endpoints {
139+
if e.SecurityPolicyURI == secPolicy && e.SecurityMode == secMode && (serverEndpoint == nil || e.SecurityLevel >= serverEndpoint.SecurityLevel) {
140+
serverEndpoint = e
141+
}
142+
}
143+
}
144+
145+
*cfg.Policy = savePolicy
146+
147+
if serverEndpoint == nil {
148+
return nil, fmt.Errorf("unable to find suitable server.Endpoint with selected sec.Policy and sec.Mode")
149+
}
150+
151+
if validateEndpointConfig(endpoints, serverEndpoint.SecurityPolicyURI, serverEndpoint.SecurityMode, authMode); err != nil {
152+
return nil, fmt.Errorf("error validating input: %s", err)
153+
}
154+
155+
opts = append(opts, opcua.SecurityFromEndpoint(serverEndpoint, authMode))
156+
logger.Info("using config: Endpoint: %s, Security.Mode: %s, %s auth.Mode : %s", serverEndpoint.EndpointURL, serverEndpoint.SecurityPolicyURI, serverEndpoint.SecurityMode, authMode)
157+
return opts, nil
158+
}
159+
160+
func validateEndpointConfig(endpoints []*ua.EndpointDescription, secPolicy string, secMode ua.MessageSecurityMode, authMode ua.UserTokenType) error {
161+
for _, e := range endpoints {
162+
if e.SecurityMode == secMode && e.SecurityPolicyURI == secPolicy {
163+
for _, t := range e.UserIdentityTokens {
164+
if t.TokenType == authMode {
165+
return nil
166+
}
167+
}
168+
}
169+
}
170+
return errors.Errorf("server does not support an.Endpoint with security : %s , %s", secPolicy, secMode)
171+
}

0 commit comments

Comments
 (0)