|
0 |
package scada
|
|
1 |
|
|
2 |
import (
|
|
3 |
"crypto/tls"
|
|
4 |
"errors"
|
|
5 |
"fmt"
|
|
6 |
"io"
|
|
7 |
"net"
|
|
8 |
"os"
|
|
9 |
"sync"
|
|
10 |
"time"
|
|
11 |
|
|
12 |
sc "github.com/hashicorp/scada-client"
|
|
13 |
)
|
|
14 |
|
|
15 |
// Provider wraps scada-client.Provider to allow most applications to only pull
|
|
16 |
// in this package
|
|
17 |
type Provider struct {
|
|
18 |
*sc.Provider
|
|
19 |
}
|
|
20 |
|
|
21 |
type AtlasConfig struct {
|
|
22 |
// Endpoint is the SCADA endpoint used for Atlas integration. If empty, the
|
|
23 |
// defaults from the provider are used.
|
|
24 |
Endpoint string `mapstructure:"endpoint"`
|
|
25 |
|
|
26 |
// The name of the infrastructure we belong to, e.g. "hashicorp/prod"
|
|
27 |
Infrastructure string `mapstructure:"infrastructure"`
|
|
28 |
|
|
29 |
// The Atlas authentication token
|
|
30 |
Token string `mapstructure:"token" json:"-"`
|
|
31 |
}
|
|
32 |
|
|
33 |
// Config holds the high-level information used to instantiate a SCADA provider
|
|
34 |
// and listener
|
|
35 |
type Config struct {
|
|
36 |
// The service name to use
|
|
37 |
Service string
|
|
38 |
|
|
39 |
// The version of the service
|
|
40 |
Version string
|
|
41 |
|
|
42 |
// The type of resource we represent
|
|
43 |
ResourceType string
|
|
44 |
|
|
45 |
// Metadata to send to along with the service information
|
|
46 |
Meta map[string]string
|
|
47 |
|
|
48 |
// If set, TLS certificate verification will be skipped. The value of the
|
|
49 |
// SCADA_INSECURE environment variable will be considered if this is false.
|
|
50 |
// If using SCADA_INSECURE, any non-empty value will trigger insecure mode.
|
|
51 |
Insecure bool
|
|
52 |
|
|
53 |
// Holds Atlas configuration
|
|
54 |
Atlas AtlasConfig
|
|
55 |
}
|
|
56 |
|
|
57 |
// ProviderService returns the service information for the provider
|
|
58 |
func providerService(c *Config) *sc.ProviderService {
|
|
59 |
ret := &sc.ProviderService{
|
|
60 |
Service: c.Service,
|
|
61 |
ServiceVersion: c.Version,
|
|
62 |
Capabilities: map[string]int{},
|
|
63 |
Meta: c.Meta,
|
|
64 |
ResourceType: c.ResourceType,
|
|
65 |
}
|
|
66 |
|
|
67 |
return ret
|
|
68 |
}
|
|
69 |
|
|
70 |
// providerConfig returns the configuration for the SCADA provider
|
|
71 |
func providerConfig(c *Config) *sc.ProviderConfig {
|
|
72 |
ret := &sc.ProviderConfig{
|
|
73 |
Service: providerService(c),
|
|
74 |
Handlers: map[string]sc.CapabilityProvider{},
|
|
75 |
Endpoint: c.Atlas.Endpoint,
|
|
76 |
ResourceGroup: c.Atlas.Infrastructure,
|
|
77 |
Token: c.Atlas.Token,
|
|
78 |
}
|
|
79 |
|
|
80 |
// SCADA_INSECURE env variable is used for testing to disable TLS
|
|
81 |
// certificate verification.
|
|
82 |
insecure := c.Insecure
|
|
83 |
if !insecure {
|
|
84 |
if os.Getenv("SCADA_INSECURE") != "" {
|
|
85 |
insecure = true
|
|
86 |
}
|
|
87 |
}
|
|
88 |
if insecure {
|
|
89 |
ret.TLSConfig = &tls.Config{
|
|
90 |
InsecureSkipVerify: true,
|
|
91 |
}
|
|
92 |
}
|
|
93 |
|
|
94 |
return ret
|
|
95 |
}
|
|
96 |
|
|
97 |
// NewProvider creates a new SCADA provider using the given configuration.
|
|
98 |
// Requests for the HTTP capability are passed off to the listener that is
|
|
99 |
// returned.
|
|
100 |
func NewHTTPProvider(c *Config, logOutput io.Writer) (*Provider, net.Listener, error) {
|
|
101 |
// Get the configuration of the provider
|
|
102 |
config := providerConfig(c)
|
|
103 |
config.LogOutput = logOutput
|
|
104 |
|
|
105 |
// Set the HTTP capability
|
|
106 |
config.Service.Capabilities["http"] = 1
|
|
107 |
|
|
108 |
// Create an HTTP listener and handler
|
|
109 |
list := newScadaListener(c.Atlas.Infrastructure)
|
|
110 |
config.Handlers["http"] = func(capability string, meta map[string]string,
|
|
111 |
conn io.ReadWriteCloser) error {
|
|
112 |
return list.PushRWC(conn)
|
|
113 |
}
|
|
114 |
|
|
115 |
// Create the provider
|
|
116 |
provider, err := sc.NewProvider(config)
|
|
117 |
if err != nil {
|
|
118 |
list.Close()
|
|
119 |
return nil, nil, err
|
|
120 |
}
|
|
121 |
|
|
122 |
return &Provider{provider}, list, nil
|
|
123 |
}
|
|
124 |
|
|
125 |
// scadaListener is used to return a net.Listener for
|
|
126 |
// incoming SCADA connections
|
|
127 |
type scadaListener struct {
|
|
128 |
addr *scadaAddr
|
|
129 |
pending chan net.Conn
|
|
130 |
|
|
131 |
closed bool
|
|
132 |
closedCh chan struct{}
|
|
133 |
l sync.Mutex
|
|
134 |
}
|
|
135 |
|
|
136 |
// newScadaListener returns a new listener
|
|
137 |
func newScadaListener(infra string) *scadaListener {
|
|
138 |
l := &scadaListener{
|
|
139 |
addr: &scadaAddr{infra},
|
|
140 |
pending: make(chan net.Conn),
|
|
141 |
closedCh: make(chan struct{}),
|
|
142 |
}
|
|
143 |
return l
|
|
144 |
}
|
|
145 |
|
|
146 |
// PushRWC is used to push a io.ReadWriteCloser as a net.Conn
|
|
147 |
func (s *scadaListener) PushRWC(conn io.ReadWriteCloser) error {
|
|
148 |
// Check if this already implements net.Conn
|
|
149 |
if nc, ok := conn.(net.Conn); ok {
|
|
150 |
return s.Push(nc)
|
|
151 |
}
|
|
152 |
|
|
153 |
// Wrap to implement the interface
|
|
154 |
wrapped := &scadaRWC{conn, s.addr}
|
|
155 |
return s.Push(wrapped)
|
|
156 |
}
|
|
157 |
|
|
158 |
// Push is used to add a connection to the queu
|
|
159 |
func (s *scadaListener) Push(conn net.Conn) error {
|
|
160 |
select {
|
|
161 |
case s.pending <- conn:
|
|
162 |
return nil
|
|
163 |
case <-time.After(time.Second):
|
|
164 |
return fmt.Errorf("accept timed out")
|
|
165 |
case <-s.closedCh:
|
|
166 |
return fmt.Errorf("scada listener closed")
|
|
167 |
}
|
|
168 |
}
|
|
169 |
|
|
170 |
func (s *scadaListener) Accept() (net.Conn, error) {
|
|
171 |
select {
|
|
172 |
case conn := <-s.pending:
|
|
173 |
return conn, nil
|
|
174 |
case <-s.closedCh:
|
|
175 |
return nil, fmt.Errorf("scada listener closed")
|
|
176 |
}
|
|
177 |
}
|
|
178 |
|
|
179 |
func (s *scadaListener) Close() error {
|
|
180 |
s.l.Lock()
|
|
181 |
defer s.l.Unlock()
|
|
182 |
if s.closed {
|
|
183 |
return nil
|
|
184 |
}
|
|
185 |
s.closed = true
|
|
186 |
close(s.closedCh)
|
|
187 |
return nil
|
|
188 |
}
|
|
189 |
|
|
190 |
func (s *scadaListener) Addr() net.Addr {
|
|
191 |
return s.addr
|
|
192 |
}
|
|
193 |
|
|
194 |
// scadaAddr is used to return a net.Addr for SCADA
|
|
195 |
type scadaAddr struct {
|
|
196 |
infra string
|
|
197 |
}
|
|
198 |
|
|
199 |
func (s *scadaAddr) Network() string {
|
|
200 |
return "SCADA"
|
|
201 |
}
|
|
202 |
|
|
203 |
func (s *scadaAddr) String() string {
|
|
204 |
return fmt.Sprintf("SCADA::Atlas::%s", s.infra)
|
|
205 |
}
|
|
206 |
|
|
207 |
type scadaRWC struct {
|
|
208 |
io.ReadWriteCloser
|
|
209 |
addr *scadaAddr
|
|
210 |
}
|
|
211 |
|
|
212 |
func (s *scadaRWC) LocalAddr() net.Addr {
|
|
213 |
return s.addr
|
|
214 |
}
|
|
215 |
|
|
216 |
func (s *scadaRWC) RemoteAddr() net.Addr {
|
|
217 |
return s.addr
|
|
218 |
}
|
|
219 |
|
|
220 |
func (s *scadaRWC) SetDeadline(t time.Time) error {
|
|
221 |
return errors.New("SCADA.Conn does not support deadlines")
|
|
222 |
}
|
|
223 |
|
|
224 |
func (s *scadaRWC) SetReadDeadline(t time.Time) error {
|
|
225 |
return errors.New("SCADA.Conn does not support deadlines")
|
|
226 |
}
|
|
227 |
|
|
228 |
func (s *scadaRWC) SetWriteDeadline(t time.Time) error {
|
|
229 |
return errors.New("SCADA.Conn does not support deadlines")
|
|
230 |
}
|