|
1 | 1 | package client
|
2 | 2 |
|
3 | 3 | import (
|
4 |
| - "context" |
5 |
| - "fmt" |
6 |
| - "io/ioutil" |
| 4 | + "crypto/rsa" |
| 5 | + "crypto/tls" |
7 | 6 | "strings"
|
8 | 7 | "time"
|
9 | 8 |
|
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" |
13 | 11 | "github.com/skilld-labs/telemetry-opcua-exporter/config"
|
14 | 12 | "github.com/skilld-labs/telemetry-opcua-exporter/log"
|
15 | 13 | )
|
16 | 14 |
|
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) |
35 | 18 |
|
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)...) |
39 | 23 |
|
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) |
47 | 25 |
|
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...) |
53 | 27 | }
|
54 | 28 |
|
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) |
61 | 31 | 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) |
66 | 33 | }
|
67 |
| - return opts |
68 |
| -} |
69 | 34 |
|
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 |
79 | 36 | 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 |
87 | 47 | default:
|
88 |
| - return nil, fmt.Errorf("invalid security.Policy: %s", *cfg.Policy) |
| 48 | + l.Fatal("invalid security policy: %s", c.SecPolicy) |
89 | 49 | }
|
90 | 50 |
|
91 |
| - var secMode ua.MessageSecurityMode |
92 |
| - switch strings.ToLower(*cfg.Mode) { |
| 51 | + var mode ua.MessageSecurityMode |
| 52 | + switch strings.ToLower(c.SecMode) { |
93 | 53 | case "auto":
|
94 | 54 | case "none":
|
95 |
| - secMode = ua.MessageSecurityModeNone |
96 |
| - *cfg.Mode = "" |
| 55 | + mode = ua.MessageSecurityModeNone |
97 | 56 | case "sign":
|
98 |
| - secMode = ua.MessageSecurityModeSign |
99 |
| - *cfg.Mode = "" |
| 57 | + mode = ua.MessageSecurityModeSign |
100 | 58 | case "signandencrypt":
|
101 |
| - secMode = ua.MessageSecurityModeSignAndEncrypt |
102 |
| - *cfg.Mode = "" |
| 59 | + mode = ua.MessageSecurityModeSignAndEncrypt |
103 | 60 | default:
|
104 |
| - return nil, fmt.Errorf("invalid security.Mode: %s", *cfg.Mode) |
| 61 | + l.Fatal("invalid security mode: %s", c.SecMode) |
105 | 62 | }
|
106 | 63 |
|
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 |
111 | 68 | }
|
112 | 69 |
|
113 | 70 | // Find the best endpoint based on our input and server recommendation (highest SecurityMode+SecurityLevel)
|
114 |
| - var serverEndpoint *ua.EndpointDescription |
| 71 | + var ep *ua.EndpointDescription |
115 | 72 | 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 |
120 | 77 | }
|
121 | 78 | }
|
122 | 79 |
|
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 |
127 | 84 | }
|
128 | 85 | }
|
129 | 86 |
|
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 |
134 | 91 | }
|
135 | 92 | }
|
136 | 93 |
|
137 | 94 | 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 |
141 | 98 | }
|
142 | 99 | }
|
143 | 100 | }
|
144 | 101 |
|
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 | +} |
146 | 115 |
|
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), |
149 | 121 | }
|
| 122 | +} |
150 | 123 |
|
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()) |
153 | 134 | }
|
| 135 | + o = append(o, opcua.SecurityFromEndpoint(e, ua.UserTokenTypeFromString(c.AuthMode))) |
| 136 | + return o |
| 137 | +} |
154 | 138 |
|
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 |
158 | 150 | }
|
159 | 151 |
|
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") |
167 | 162 | }
|
168 | 163 | }
|
| 164 | + return crt |
169 | 165 | }
|
170 |
| - return errors.Errorf("server does not support an.Endpoint with security : %s , %s", secPolicy, secMode) |
| 166 | + return crt |
171 | 167 | }
|
0 commit comments