/
proxy.go
223 lines (186 loc) · 7.38 KB
/
proxy.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
/*
Copyright 2018, Cossack Labs Limited
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package base
import (
"context"
"fmt"
"net"
"github.com/cossacklabs/acra/network"
acracensor "github.com/cossacklabs/acra/acra-censor"
"github.com/cossacklabs/acra/encryptor/config"
"github.com/cossacklabs/acra/keystore"
"github.com/cossacklabs/acra/sqlparser"
)
// Callback represents function to call on detecting poison record
type Callback interface {
Call() error
}
// PoisonRecordCallbackStorage stores all callbacks, on Call iterates
// and calls each callbacks until error or end of iterating
type PoisonRecordCallbackStorage interface {
Callback
AddCallback(callback Callback)
HasCallbacks() bool
}
// ProxySetting provide data access methods for proxy factories
type ProxySetting interface {
PoisonRecordCallbackStorage() PoisonRecordCallbackStorage
SQLParser() *sqlparser.Parser
KeyStore() keystore.DecryptionKeyStore
TableSchemaStore() config.TableSchemaStore
Censor() acracensor.AcraCensorInterface
TLSConnectionWrapper() TLSConnectionWrapper
}
type proxySetting struct {
keystore keystore.DecryptionKeyStore
tableSchemaStore config.TableSchemaStore
censor acracensor.AcraCensorInterface
connectionWrapper TLSConnectionWrapper
poisonRecordCallbackStorage PoisonRecordCallbackStorage
parser *sqlparser.Parser
}
// SQLParser return sqlparser.Parser
func (p *proxySetting) SQLParser() *sqlparser.Parser {
return p.parser
}
// PoisonRecordCallbackStorage return CallbackStorage
func (p *proxySetting) PoisonRecordCallbackStorage() PoisonRecordCallbackStorage {
return p.poisonRecordCallbackStorage
}
// Censor return AcraCensorInterface implementation
func (p *proxySetting) Censor() acracensor.AcraCensorInterface {
return p.censor
}
// TableSchemaStore return table schema store
func (p *proxySetting) TableSchemaStore() config.TableSchemaStore {
return p.tableSchemaStore
}
// KeyStore return keystore
func (p *proxySetting) KeyStore() keystore.DecryptionKeyStore {
return p.keystore
}
// TLSConnectionWrapper return TLSConnectionWrapper
func (p *proxySetting) TLSConnectionWrapper() TLSConnectionWrapper {
return p.connectionWrapper
}
// NewProxySetting return new ProxySetting implementation with data from params
func NewProxySetting(parser *sqlparser.Parser, tableSchema config.TableSchemaStore, keystore keystore.DecryptionKeyStore, wrapper TLSConnectionWrapper, censor acracensor.AcraCensorInterface, callbackStorage PoisonRecordCallbackStorage) ProxySetting {
return &proxySetting{
keystore: keystore, parser: parser, tableSchemaStore: tableSchema, censor: censor,
connectionWrapper: wrapper, poisonRecordCallbackStorage: callbackStorage,
}
}
// Proxy interface to process client's requests to database and responses
type Proxy interface {
QueryObservable
ClientIDObservable
ProxyClientConnection(context.Context, chan<- ProxyError)
ProxyDatabaseConnection(context.Context, chan<- ProxyError)
}
// TLSConnectionWrapper used by proxy to wrap raw connections to TLS when intercepts client/database request about switching to TLS
// Reuse network.ConnectionWrapper to explicitly force TLS usage by name
type TLSConnectionWrapper interface {
WrapDBConnection(ctx context.Context, conn net.Conn) (net.Conn, error)
WrapClientConnection(ctx context.Context, conn net.Conn) (wrappedConnection net.Conn, clientID []byte, err error)
UseConnectionClientID() bool
}
type proxyTLSConnectionWrapper struct {
wrapper network.ConnectionWrapper
useConnectionClientID bool
}
// NewTLSConnectionWrapper return wrapper over network.ConnectionWrapper to implement TLSConnectionWrapper interface
func NewTLSConnectionWrapper(useClientID bool, wrapper network.ConnectionWrapper) TLSConnectionWrapper {
return &proxyTLSConnectionWrapper{wrapper: wrapper, useConnectionClientID: useClientID}
}
func (wrapper *proxyTLSConnectionWrapper) WrapDBConnection(ctx context.Context, conn net.Conn) (net.Conn, error) {
return wrapper.wrapper.WrapClient(ctx, conn)
}
func (wrapper *proxyTLSConnectionWrapper) WrapClientConnection(ctx context.Context, conn net.Conn) (net.Conn, []byte, error) {
return wrapper.wrapper.WrapServer(ctx, conn)
}
func (wrapper *proxyTLSConnectionWrapper) UseConnectionClientID() bool {
return wrapper.useConnectionClientID
}
// ProxyFactory create new Proxy for specific database
type ProxyFactory interface {
New(clientID []byte, clientSession ClientSession) (Proxy, error)
}
// PreparedStatementRegistry keeps track of active prepared statements and cursors within a ClientSession.
type PreparedStatementRegistry interface {
AddStatement(statement PreparedStatement) error
DeleteStatement(name string) error
StatementByName(name string) (PreparedStatement, error)
AddCursor(cursor Cursor) error
DeleteCursor(name string) error
CursorByName(name string) (Cursor, error)
}
// PreparedStatement is a prepared statement, ready to be executed.
// It can be either a textual SQL statement from "PREPARE", or a database protocol equivalent.
type PreparedStatement interface {
Name() string
Query() sqlparser.Statement
QueryText() string
ParamsNum() int
}
// Cursor is used to iterate over a prepared statement.
// It can be either a textual SQL statement from "DEFINE CURSOR", or a database protocol equivalent.
type Cursor interface {
Name() string
PreparedStatement() PreparedStatement
}
const (
acraDBProxyErrSide = "AcraServer-Database"
acraClientProxyErrSide = "Client-AcraServer"
)
// ProxyError custom error type for handling all related errors on Client/DB proxies
type ProxyError struct {
sourceErr error
interruptSide string
}
// NewClientProxyError construct ProxyError object with Client interrupt side
func NewClientProxyError(err error) ProxyError {
return ProxyError{
sourceErr: err,
interruptSide: acraClientProxyErrSide,
}
}
// NewDBProxyError construct ProxyError object with DB interrupt side
func NewDBProxyError(err error) ProxyError {
return ProxyError{
sourceErr: err,
interruptSide: acraDBProxyErrSide,
}
}
func (p ProxyError) Error() string {
return fmt.Sprintf("%s:%s", p.interruptSide, p.sourceErr.Error())
}
// Unwrap return the source error
func (p ProxyError) Unwrap() error {
return p.sourceErr
}
// InterruptSide return interruption side where error happened
func (p ProxyError) InterruptSide() string {
return p.interruptSide
}
// OnlyDefaultEncryptorSettings returns true if config contains settings only for transparent decryption that works by default
func OnlyDefaultEncryptorSettings(store config.TableSchemaStore) bool {
storeMask := store.GetGlobalSettingsMask()
return storeMask&(config.SettingSearchFlag|
config.SettingMaskingFlag|
config.SettingTokenizationFlag|
config.SettingDefaultDataValueFlag|
config.SettingDataTypeFlag) == 0
}
// AcraCensorBlockedThisQuery is an error message, that is sent to the user in case of
// query blockage
const AcraCensorBlockedThisQuery = "AcraCensor blocked this query"