Skip to content

Commit a5f3c64

Browse files
jbguerrazJean-Baptiste Guerraz
authored andcommitted
Client refactoring
1 parent 438f669 commit a5f3c64

File tree

6 files changed

+157
-164
lines changed

6 files changed

+157
-164
lines changed

client/client.go

Lines changed: 109 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,171 +1,167 @@
11
package client
22

33
import (
4-
"context"
5-
"fmt"
6-
"io/ioutil"
4+
"crypto/rsa"
5+
"crypto/tls"
76
"strings"
87
"time"
98

10-
"github.com/skilld-labs/opcua"
11-
"github.com/skilld-labs/opcua/errors"
12-
"github.com/skilld-labs/opcua/ua"
9+
"github.com/gopcua/opcua"
10+
"github.com/gopcua/opcua/ua"
1311
"github.com/skilld-labs/telemetry-opcua-exporter/config"
1412
"github.com/skilld-labs/telemetry-opcua-exporter/log"
1513
)
1614

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-
}
15+
func NewClientFromServerConfig(c config.ServerConfig, l log.Logger) *opcua.Client {
16+
e := findEndpoint(c, l)
17+
crt := loadCertificate(c, l)
3518

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-
}
19+
o := []opcua.Option{}
20+
o = append(o, connectionOptions()...)
21+
o = append(o, authenticationOptions(c, l, e, &crt)...)
22+
o = append(o, securityOptions(c, l, e, &crt)...)
3923

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))
24+
l.Info("client using config: Endpoint: %s, Security Mode: %s, %s, Authentication Mode : %s", e.EndpointURL, e.SecurityPolicyURI, e.SecurityMode, c.AuthMode)
4725

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
26+
return opcua.NewClient(c.Endpoint, o...)
5327
}
5428

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)
29+
func findEndpoint(c config.ServerConfig, l log.Logger) *ua.EndpointDescription {
30+
ee, err := opcua.GetEndpoints(c.Endpoint)
6131
if err != nil {
62-
logger.Err("cannot read certfile : %v", err)
63-
}
64-
if certfile != "" {
65-
opts = append(opts, opcua.Certificate(cert))
32+
l.Fatal("get endpoints failed: %v", err)
6633
}
67-
return opts
68-
}
6934

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
35+
var policy string
7936
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 = ""
37+
case c.SecPolicy == "auto":
38+
case strings.HasPrefix(c.SecPolicy, ua.SecurityPolicyURIPrefix):
39+
policy = c.SecPolicy
40+
case c.SecPolicy == "None" ||
41+
c.SecPolicy == "Basic128Rsa15" ||
42+
c.SecPolicy == "Basic256" ||
43+
c.SecPolicy == "Basic256Sha256" ||
44+
c.SecPolicy == "Aes128_Sha256_RsaOaep" ||
45+
c.SecPolicy == "Aes256_Sha256_RsaPss":
46+
policy = ua.SecurityPolicyURIPrefix + c.SecPolicy
8747
default:
88-
return nil, fmt.Errorf("invalid security.Policy: %s", *cfg.Policy)
48+
l.Fatal("invalid security policy: %s", c.SecPolicy)
8949
}
9050

91-
var secMode ua.MessageSecurityMode
92-
switch strings.ToLower(*cfg.Mode) {
51+
var mode ua.MessageSecurityMode
52+
switch strings.ToLower(c.SecMode) {
9353
case "auto":
9454
case "none":
95-
secMode = ua.MessageSecurityModeNone
96-
*cfg.Mode = ""
55+
mode = ua.MessageSecurityModeNone
9756
case "sign":
98-
secMode = ua.MessageSecurityModeSign
99-
*cfg.Mode = ""
57+
mode = ua.MessageSecurityModeSign
10058
case "signandencrypt":
101-
secMode = ua.MessageSecurityModeSignAndEncrypt
102-
*cfg.Mode = ""
59+
mode = ua.MessageSecurityModeSignAndEncrypt
10360
default:
104-
return nil, fmt.Errorf("invalid security.Mode: %s", *cfg.Mode)
61+
l.Fatal("invalid security mode: %s", c.SecMode)
10562
}
10663

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
64+
// Allow input of only one of security mode or security policy when choosing 'None'
65+
if mode == ua.MessageSecurityModeNone || policy == ua.SecurityPolicyURINone {
66+
mode = ua.MessageSecurityModeNone
67+
policy = ua.SecurityPolicyURINone
11168
}
11269

11370
// Find the best endpoint based on our input and server recommendation (highest SecurityMode+SecurityLevel)
114-
var serverEndpoint *ua.EndpointDescription
71+
var ep *ua.EndpointDescription
11572
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
73+
case c.SecMode == "auto" && c.SecPolicy == "auto": // No user selection, choose best
74+
for _, e := range ee {
75+
if ep == nil || (e.SecurityMode >= ep.SecurityMode && e.SecurityLevel >= ep.SecurityLevel) {
76+
ep = e
12077
}
12178
}
12279

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
80+
case c.SecMode != "auto" && c.SecPolicy == "auto": // User only cares about.Mode, select highest securitylevel with that.Mode
81+
for _, e := range ee {
82+
if e.SecurityMode == mode && (ep == nil || e.SecurityLevel >= ep.SecurityLevel) {
83+
ep = e
12784
}
12885
}
12986

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
87+
case c.SecMode == "auto" && c.SecPolicy != "auto": // User only cares about.Policy, select highest securitylevel with that.Policy
88+
for _, e := range ee {
89+
if e.SecurityPolicyURI == policy && (ep == nil || e.SecurityLevel >= ep.SecurityLevel) {
90+
ep = e
13491
}
13592
}
13693

13794
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
95+
for _, e := range ee {
96+
if e.SecurityPolicyURI == policy && e.SecurityMode == mode && (ep == nil || e.SecurityLevel >= ep.SecurityLevel) {
97+
ep = e
14198
}
14299
}
143100
}
144101

145-
*cfg.Policy = savePolicy
102+
if ep != nil {
103+
// Make sure the selected endpoint supports the authentication mode
104+
utt := ua.UserTokenTypeFromString(c.AuthMode)
105+
for _, t := range ep.UserIdentityTokens {
106+
if t.TokenType == utt {
107+
return ep
108+
}
109+
}
110+
}
111+
112+
l.Fatal("unable to find suitable server endpoint with selected security policy, security mode and authentication mode")
113+
return nil
114+
}
146115

147-
if serverEndpoint == nil {
148-
return nil, fmt.Errorf("unable to find suitable server.Endpoint with selected sec.Policy and sec.Mode")
116+
func connectionOptions() []opcua.Option {
117+
return []opcua.Option{
118+
opcua.Lifetime(10 * time.Minute),
119+
opcua.RequestTimeout(5 * time.Second),
120+
opcua.AutoReconnect(true),
149121
}
122+
}
150123

151-
if validateEndpointConfig(endpoints, serverEndpoint.SecurityPolicyURI, serverEndpoint.SecurityMode, authMode); err != nil {
152-
return nil, fmt.Errorf("error validating input: %s", err)
124+
func authenticationOptions(c config.ServerConfig, l log.Logger, e *ua.EndpointDescription, crt *tls.Certificate) []opcua.Option {
125+
o := []opcua.Option{}
126+
switch c.AuthMode {
127+
case "Certificate":
128+
o = append(o, opcua.AuthCertificate(crt.Certificate[0]))
129+
case "UserName":
130+
o = append(o, opcua.AuthUsername(c.Username, c.Password))
131+
default:
132+
l.Info("authentication mode not set, defaulting to anonymous")
133+
o = append(o, opcua.AuthAnonymous())
153134
}
135+
o = append(o, opcua.SecurityFromEndpoint(e, ua.UserTokenTypeFromString(c.AuthMode)))
136+
return o
137+
}
154138

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
139+
func securityOptions(c config.ServerConfig, l log.Logger, e *ua.EndpointDescription, crt *tls.Certificate) []opcua.Option {
140+
o := []opcua.Option{}
141+
switch c.SecMode {
142+
case "Sign", "SignAndEncrypt":
143+
o = append(o,
144+
opcua.PrivateKey(crt.PrivateKey.(*rsa.PrivateKey)),
145+
opcua.Certificate(crt.Certificate[0]))
146+
default:
147+
l.Warn("No security mode is not recommended, consider using one")
148+
}
149+
return o
158150
}
159151

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-
}
152+
func loadCertificate(c config.ServerConfig, l log.Logger) tls.Certificate {
153+
var crt tls.Certificate
154+
if c.CertPath != "" && c.KeyPath != "" {
155+
crt, err := tls.LoadX509KeyPair(c.CertPath, c.KeyPath)
156+
if err != nil {
157+
l.Fatal("failed to load certificate: %s", err)
158+
} else {
159+
_, ok := crt.PrivateKey.(*rsa.PrivateKey)
160+
if !ok {
161+
l.Fatal("invalid private key")
167162
}
168163
}
164+
return crt
169165
}
170-
return errors.Errorf("server does not support an.Endpoint with security : %s , %s", secPolicy, secMode)
166+
return crt
171167
}

collector/collector.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package collector
22

33
import (
4+
"context"
45
"fmt"
56
"time"
67

7-
"github.com/skilld-labs/opcua"
8-
"github.com/skilld-labs/opcua/ua"
8+
"github.com/gopcua/opcua"
9+
"github.com/gopcua/opcua/ua"
910
"github.com/prometheus/client_golang/prometheus"
1011
"github.com/skilld-labs/telemetry-opcua-exporter/client"
1112
"github.com/skilld-labs/telemetry-opcua-exporter/config"
@@ -38,6 +39,9 @@ type metric struct {
3839
func NewCollector(cfg *CollectorConfig) (*Collector, error) {
3940
c := &Collector{Logger: cfg.Logger, ServerConfig: *cfg.Config.ServerConfig}
4041
c.opcuaClient = client.NewClientFromServerConfig(*cfg.Config.ServerConfig, cfg.Logger)
42+
if err := c.opcuaClient.Connect(context.Background()); err != nil {
43+
c.Logger.Fatal("cannot connect opcua client %v", err)
44+
}
4145
c.ReloadMetrics(cfg.Config.MetricsConfig)
4246
return c, nil
4347
}

config/config.go

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ type Config struct {
1515
}
1616

1717
type ServerConfig struct {
18-
Endpoint *string
19-
CertFile *string
20-
KeyFile *string
21-
Policy *string
22-
Mode *string
23-
Auth *string
24-
Username *string
25-
Password *string
18+
Endpoint string
19+
CertPath string
20+
KeyPath string
21+
SecPolicy string
22+
SecMode string
23+
AuthMode string
24+
Username string
25+
Password string
2626
}
2727

2828
type MetricsConfig struct {
@@ -37,21 +37,21 @@ type Metric struct {
3737
Type string `yaml:"type"`
3838
}
3939

40-
func NewConfig(endpoint, certfile, keyfile, policy, mode, auth, username, password, filename *string) (*Config, error) {
40+
func NewConfig(endpoint, certPath, keyPath, secMode, secPolicy, authMode, username, password, configPath string) (*Config, error) {
4141
c := &Config{
4242
ServerConfig: &ServerConfig{
43-
Endpoint: endpoint,
44-
CertFile: certfile,
45-
KeyFile: keyfile,
46-
Policy: policy,
47-
Mode: mode,
48-
Auth: auth,
49-
Username: username,
50-
Password: password,
43+
Endpoint: endpoint,
44+
CertPath: certPath,
45+
KeyPath: keyPath,
46+
SecMode: secMode,
47+
SecPolicy: secPolicy,
48+
AuthMode: authMode,
49+
Username: username,
50+
Password: password,
5151
},
5252
MetricsConfig: &MetricsConfig{},
5353
}
54-
if err := c.LoadMetricsConfig(*filename); err != nil {
54+
if err := c.LoadMetricsConfig(configPath); err != nil {
5555
return nil, err
5656
}
5757
return c, nil

go.mod

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@ module github.com/skilld-labs/telemetry-opcua-exporter
33
go 1.14
44

55
require (
6-
github.com/davecgh/go-spew v1.1.1
6+
github.com/gopcua/opcua v0.1.14-0.20201026203904-26ad3a299045
77
github.com/prometheus/client_golang v1.8.0
88
github.com/prometheus/common v0.14.0
9-
github.com/skilld-labs/opcua v0.1.14-0.20201021101852-d9339e13deb1
109
gopkg.in/yaml.v2 v2.3.0
1110
)

0 commit comments

Comments
 (0)