Skip to content

Commit 354c931

Browse files
authored
Merge pull request #28 from cipherstash/chore/update-xorm-example-readme
Update go/xorm README with more detail on integrating EQL
2 parents 37f02bc + b76eb2a commit 354c931

File tree

1 file changed

+165
-0
lines changed

1 file changed

+165
-0
lines changed

languages/go/xorm/README.md

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,168 @@ Run tests:
7979
```shell
8080
./run.sh tests
8181
```
82+
83+
## Integrating EQL into a Xorm app
84+
85+
Before starting to integrate, follow the EQL installation steps in the main [README file](../../../README.md).
86+
87+
The [goeql package](https://github.com/cipherstash/encrypt-query-language/blob/main/languages/go/goeql/goeql.go) contains functions to help with serializing data into the format that CipherStash Proxy expects and deserializing data from this format back to the original value.
88+
89+
For reference there is an example setup in the [main.go](./main.go) file.
90+
91+
Example migrations are in the [migrations.go](./migrations.go) file.
92+
93+
Start with adding a new encrypted field:
94+
95+
1. Add a custom type for the field.
96+
97+
For example a text field:
98+
99+
```go
100+
type EncryptedTextField string
101+
```
102+
103+
jsonb field:
104+
105+
```go
106+
type EncryptedJsonbField map[string]interface{}
107+
```
108+
109+
2. Add the field/s to the relevant struct:
110+
111+
```go
112+
type Example struct {
113+
Id int64 `xorm:"pk autoincr"`
114+
EncryptedTextField EncryptedTextField `json:"encrypted_text_field" xorm:"jsonb 'encrypted_text_field'"`
115+
EncryptedJsonbField EncryptedJsonbField `json:"encrypted_jsonb_field" xorm:"jsonb 'encrypted_jsonb_field'"`
116+
}
117+
```
118+
119+
3. Use the conversion interface to define a custom mapping rule for each field.
120+
121+
Within each function use the goeql Serialize and Deserialize functions.
122+
123+
When serializing the table name and column name need to be passed as arguments.
124+
125+
Example for a text field:
126+
127+
```go
128+
func (et EncryptedTextField) ToDB() ([]byte, error) {
129+
etCs := goeql.EncryptedText(et)
130+
// e.g table name is "examples" and field is "encrypted_text_field"
131+
return (&etCs).Serialize("examples", "encrypted_text_field")
132+
}
133+
134+
func (et *EncryptedTextField) FromDB(data []byte) error {
135+
etCs := goeql.EncryptedText(*et)
136+
137+
val, err := (&etCs).Deserialize(data)
138+
if err != nil {
139+
return err
140+
}
141+
142+
*et = EncryptedTextField(val)
143+
144+
return nil
145+
}
146+
```
147+
148+
Example for a jsonb field:
149+
150+
```go
151+
func (ej EncryptedJsonbField) ToDB() ([]byte, error) {
152+
ejCs := goeql.EncryptedJsonb(ej)
153+
// e.g table name is "examples" and field is "encrypted_jsonb_field"
154+
return (&ejCs).Serialize("examples", "encrypted_jsonb_field")
155+
}
156+
157+
func (ej *EncryptedJsonbField) FromDB(data []byte) error {
158+
etCs := goeql.EncryptedJsonb(*ej)
159+
160+
val, err := (&etCs).Deserialize(data)
161+
if err != nil {
162+
return err
163+
}
164+
165+
*ej = EncryptedJsonbField(val)
166+
167+
return nil
168+
}
169+
```
170+
171+
4. Add a migration to add custom constraint checks for each field.
172+
173+
These checks will validate that the json payload is correct and that encrypted data is being inserted correctly.
174+
175+
Example:
176+
177+
```sql
178+
ALTER TABLE examples ADD CONSTRAINT encrypted_text_field_encrypted_check
179+
CHECK ( cs_check_encrypted_v1(encrypted_text_field) );
180+
181+
ALTER TABLE examples ADD CONSTRAINT encrypted_jsonb_encrypted_check
182+
CHECK ( cs_check_encrypted_v1(encrypted_jsonb_field) );
183+
```
184+
185+
5. [Add indexes](../../../README.md#managing-indexes-with-eql):
186+
187+
Example:
188+
189+
```sql
190+
SELECT cs_add_index_v1('examples', 'encrypted_text_field', 'unique', 'text', '{"token_filters": [{"kind": "downcase"}]}');
191+
SELECT cs_add_index_v1('examples', 'encrypted_text_field', 'match', 'text');
192+
SELECT cs_add_index_v1('examples', 'encrypted_text_field', 'ore', 'text');
193+
SELECT cs_add_index_v1('examples', 'encrypted_jsonb_field', 'ste_vec', 'jsonb', '{"prefix": "some-prefix"}');
194+
195+
-- The below indexes will also need to be added to enable full search functionality on the encrypted columns
196+
197+
CREATE UNIQUE INDEX ON examples(cs_unique_v1(encrypted_text_field));
198+
CREATE INDEX ON examples USING GIN (cs_match_v1(encrypted_text_field));
199+
CREATE INDEX ON examples (cs_ore_64_8_v1(encrypted_text_field));
200+
CREATE INDEX ON examples USING GIN (cs_ste_vec_v1(encrypted_jsonb_field));
201+
202+
-- Run these functions to activate
203+
204+
SELECT cs_encrypt_v1();
205+
SELECT cs_activate_v1();
206+
```
207+
208+
## Inserting
209+
210+
Inserting data remains the same.
211+
212+
The `toDB()` function that was setup in [this earlier step](README.md#integrating-eql-into-a-xorm-app), serializes the plaintext value into the json payload CipherStash Proxy expects.
213+
214+
Retrieving data remains the same as well.
215+
216+
The `fromDb()` function for the relevant encrypted field will deserialize the json payload returned from CipherStash Proxy and return the plaintext value
217+
218+
## Querying
219+
220+
The queries to retrieve data do change.
221+
222+
EQL provides specialized functions to interact with encrypted data.
223+
224+
You can read about these functions [here](../../../README.md#querying-data-with-eql).
225+
226+
Similar to how CipherStash Proxy require's a specific json payload when inserting data, a similar payload is required when querying.
227+
228+
Goeql has functions that will serialize a value into the format required by CipherStash Proxy.
229+
230+
[These functions](https://github.com/cipherstash/encrypt-query-language/blob/main/languages/go/goeql/goeql.go#L153-L171) will need to be used for the relevant query.
231+
232+
Examples of how to use these are in the [example_queries.go](./example_queries.go) file.
233+
234+
Below is an example of running a match query on a text field.
235+
236+
```go
237+
query, errTwo := goeql.MatchQuery("some", "examples", "encrypted_text_field")
238+
if errTwo != nil {
239+
log.Fatalf("Error marshaling encrypted_text_field: %v", errTwo)
240+
}
241+
242+
has, errThree := engine.Where("cs_match_v1(encrypted_text_field) @> cs_match_v1(?)", query).Get(&ExampleTwo)
243+
if errThree != nil {
244+
log.Fatalf("Could not retrieve exampleTwo: %v", errThree)
245+
}
246+
```

0 commit comments

Comments
 (0)