-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathPostgresDataMasker.cs
175 lines (154 loc) · 7.14 KB
/
PostgresDataMasker.cs
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
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using Newtonsoft.Json.Linq;
using Npgsql;
using PgMaskingProxy.Helpers;
using PgMaskingProxy.Maskers;
using PgMaskingProxy.Models;
namespace PgMaskingProxy
{
public class PostgresDataMasker
{
private TcpProxy _tcpProxy;
private readonly PostgresBackendStateMachine _pgStateMachine;
private ConfigState _state;
private ConfigState _nextState;
private readonly FileSystemWatcher _fileWatcher = new FileSystemWatcher();
private bool _configHasChanged = false;
public PostgresDataMasker()
{
DbProviderFactories.RegisterFactory("Npgsql", Npgsql.NpgsqlFactory.Instance);
_pgStateMachine = new PostgresBackendStateMachine(this.shouldModifyTable, this.modifyDataRow);
_fileWatcher.Path = Directory.GetCurrentDirectory();
_fileWatcher.Filter="config.json";
_fileWatcher.NotifyFilter = NotifyFilters.LastWrite;
_fileWatcher.Changed += (s,e) => {
_configHasChanged = true;
};
_fileWatcher.EnableRaisingEvents = true;
loadState();
}
private void loadState()
{
Console.WriteLine("Starting Proxy...");
while(true)
{
try
{
_nextState = new ConfigState();
_nextState._maskingModel = ConfigHelper.populateMaskingModel();
_nextState._oidToTableSchemaName = ConfigHelper.populateTableOidsMapper();
_nextState._nonSystemTableOids = ConfigHelper.populateNonSystemTableOids(_nextState._oidToTableSchemaName);
_nextState._oidToDataType = ConfigHelper.populateDataTypeOids();
_nextState._keyedColumns = ConfigHelper.populateAllPksAndFks();
_state = _nextState;
_nextState = null;
break;
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
Console.WriteLine("Failed to start. This is most likely because we failed to connect to your database to run some preliminary queries. Ensure the db_connection_details in config.json are correct and that your database is reachable from this machine. Trying again 5 seconds...");
Thread.Sleep(5000);
}
}
}
public void Start()
{
if(!File.Exists("config.json"))
{
Console.WriteLine("Cannot find config.json");
throw new Exception("Cannot find config.json");
}
var j = JObject.Parse(File.ReadAllText("config.json"));
int proxyPort = (int)j["proxy_port"];
string proxySourceIp = (string)j["proxy_source_ip"];
if(String.IsNullOrEmpty(proxySourceIp))
{
proxySourceIp = "127.0.0.1";
}
var dbDetails = (JObject)j["db_connection_details"];
string dbIp = (string)dbDetails["ip"];
int dbPort = (int)dbDetails["port"];
string db = (string)dbDetails["database"];
string user = (string)dbDetails["user"];
Console.WriteLine("Proxy Running:");
Console.WriteLine($"\tProxy Port: {proxyPort}");
Console.WriteLine($"\tDatabase Details: {user}@{dbIp}:{dbPort}/{db}");
_tcpProxy = new TcpProxy(_pgStateMachine.ProcessBuffer, _pgStateMachine.SetStateToInitial);
_tcpProxy.Start(new IPEndPoint(IPAddress.Parse(proxySourceIp), proxyPort), new IPEndPoint(IPAddress.Parse(dbIp), dbPort));
}
private Func<string,string> getMaskingFunction(uint tableOid, uint dataTypeOid, string columnName)
{
if(_configHasChanged)
{
loadState();
_configHasChanged = false;
}
(string table, string schema) = _state._oidToTableSchemaName[tableOid];
var maskedColumn = _state._maskingModel.MaskedColumns.FirstOrDefault(x=>x.ColumnName==columnName && x.TableName == table && x.SchemaName == schema);
if(maskedColumn!=null)
{
return maskedColumn.MaskingFunction;
}
var dataType = Enum.GetName(typeof(PostgresDataTypes), _state._oidToDataType[dataTypeOid]);
var maskedDataType = _state._maskingModel.MaskedDataTypes.FirstOrDefault(x=>x.DataType==dataType);
if(maskedDataType!=null)
{
return maskedDataType.MaskingFunction;
}
return (s) => s;
}
private MemoryStream modifyDataRow(DataRowMessage dataRow, RowDescriptionMessage rowDescription)
{
Func<string,string> maskingFunction;
var ms = new MemoryStream();
ms.Write(EndianHelpers.ToBigE(dataRow.NumFields));
int i = 0;
foreach(var f in dataRow.DataRowFields)
{
uint tableOid = rowDescription.FieldDescriptions[i].ObjectId;
string columnName = rowDescription.FieldDescriptions[i].FieldName;
uint dataTypeOid = rowDescription.FieldDescriptions[i].DataTypeObjectId;
//If the column vale is null or we are preserving keyed column values and the column is either a primary or foreign key.
if(f.ColumnValueLength==-1 || (_state._maskingModel.PreserveKeys && _state.isColumnKeyed(tableOid, columnName) ) )
{
ms.Write(EndianHelpers.ToBigE(f.ColumnValueLength));
ms.Write(f.ColumnValue);
}
else
{
maskingFunction = getMaskingFunction(tableOid, dataTypeOid, columnName);
string rowValue = System.Text.Encoding.UTF8.GetString(f.ColumnValue);
byte[] newBytes = System.Text.Encoding.UTF8.GetBytes(maskingFunction(rowValue));
byte[] newLength = EndianHelpers.ToBigE(newBytes.Length);
ms.Write(newLength);
ms.Write(newBytes);
}
i++;
}
return ms;
}
public bool shouldModifyTable(uint oid)
{
return _state._nonSystemTableOids.Contains(oid);
}
private class ConfigState
{
public DataMaskingModel _maskingModel { get; set; }
public Dictionary<uint, (string table, string schema)> _oidToTableSchemaName { get; set; }
public HashSet<ColumnModel> _keyedColumns { get; set; }
public Dictionary<uint, PostgresDataTypes> _oidToDataType { get; set; }
public HashSet<uint> _nonSystemTableOids { get; set; }
public bool isColumnKeyed(uint tableOid, string columnName)
{
return _keyedColumns.Contains(new ColumnModel {Column=columnName, Table = _oidToTableSchemaName[tableOid].table, Schema = _oidToTableSchemaName[tableOid].schema});
}
}
}
}