Skip to content

Improve goeql package documentation #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Oct 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/workflows/test-goeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Test goeql

on:
push:
branches: [ main ]
paths:
- .github/workflows/test-goeql.yml
- languages/go/goeql/**
pull_request:
branches: [ main ]
paths:
- .github/workflows/test-goeql.yml
- languages/go/goeql/**

jobs:

test:
name: Run test suite
runs-on: ubuntu-latest
steps:

- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: languages/go/goeql/go.mod
cache-dependency-path: |
languages/go/goeql/go.mod

- name: Get dependencies
run: |
go get -v -t -d ./...
make cidep
working-directory: languages/go/goeql/

- name: Test
run: |
make test
working-directory: languages/go/goeql/
Empty file.
20 changes: 20 additions & 0 deletions languages/go/goeql/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
all: test

test: gotest goerrcheck gostaticcheck golint

cidep:
go install honnef.co/go/tools/cmd/staticcheck@latest
go install github.com/kisielk/errcheck@latest
go install golang.org/x/lint/golint@latest

gotest:
go test ./... -v -timeout=45s -failfast

goerrcheck:
errcheck -exclude .errcheck-excludes -ignoretests ./...

gostaticcheck:
staticcheck ./...

golint:
golint
39 changes: 27 additions & 12 deletions languages/go/goeql/goeql.go
Original file line number Diff line number Diff line change
@@ -1,37 +1,49 @@
package goeql

// This package contains helpers to use with Go/Xorm to serialize/deserialize values
// into the shape EQL and the CipherStash proxy needs to enable encryption/decryption.
// goeql is a collection of helpers for serializing and deserializing values
// into the shape EQL and the CipherStash Proxy needs to enable encryption and
// decryption of values, and search of those encrypted values while keeping them
// encrypted at all times.

// EQL expects a json format that looks like this:
//
// '{"k":"pt","p":"a string representation of the plaintext that is being encrypted","i":{"t":"table","c":"column"},"v":1}'
//
// More documentation on this format can be found at https://github.com/cipherstash/encrypt-query-language#data-format

import (
"encoding/json"
"fmt"
"strconv"
)

// TableColumn represents the table and column an encrypted value belongs to
type TableColumn struct {
T string `json:"t"`
C string `json:"c"`
}

// EncryptedColumn represents the plaintext value sent by a database client
type EncryptedColumn struct {
K string `json:"k"`
P string `json:"p"`
I TableColumn `json:"i"`
V int `json:"v"`
}

// Creating custom types for encrypted fields to enable creating methods for
// serialization/deserialization of these types.
// EncryptedText is a string value to be encrypted
type EncryptedText string

// EncryptedJsonb is a jsonb value to be encrypted
type EncryptedJsonb map[string]interface{}

// EncryptedInt is a int value to be encrypted
type EncryptedInt int

// EncryptedBool is a bool value to be encrypted
type EncryptedBool bool

// Text
// Serialize turns a EncryptedText value into a jsonb payload for CipherStash Proxy
func (et EncryptedText) Serialize(table string, column string) ([]byte, error) {
val, err := ToEncryptedColumn(string(et), table, column)
if err != nil {
Expand All @@ -40,6 +52,7 @@ func (et EncryptedText) Serialize(table string, column string) ([]byte, error) {
return json.Marshal(val)
}

// Deserialize turns a jsonb payload from CipherStash Proxy into an EncryptedText value
func (et *EncryptedText) Deserialize(data []byte) (EncryptedText, error) {
var jsonData map[string]interface{}
if err := json.Unmarshal(data, &jsonData); err != nil {
Expand All @@ -53,7 +66,7 @@ func (et *EncryptedText) Deserialize(data []byte) (EncryptedText, error) {
return "", fmt.Errorf("invalid format: missing 'p' field in JSONB")
}

// Jsonb
// Serialize turns a EncryptedJsonb value into a jsonb payload for CipherStash Proxy
func (ej EncryptedJsonb) Serialize(table string, column string) ([]byte, error) {
val, err := ToEncryptedColumn(map[string]any(ej), table, column)
if err != nil {
Expand All @@ -62,6 +75,7 @@ func (ej EncryptedJsonb) Serialize(table string, column string) ([]byte, error)
return json.Marshal(val)
}

// Deserialize turns a jsonb payload from CipherStash Proxy into an EncryptedJsonb value
func (ej *EncryptedJsonb) Deserialize(data []byte) (EncryptedJsonb, error) {
var jsonData map[string]interface{}
if err := json.Unmarshal(data, &jsonData); err != nil {
Expand All @@ -80,7 +94,7 @@ func (ej *EncryptedJsonb) Deserialize(data []byte) (EncryptedJsonb, error) {
return nil, fmt.Errorf("invalid format: missing 'p' field in JSONB")
}

// Int
// Serialize turns a EncryptedInt value into a jsonb payload for CipherStash Proxy
func (et EncryptedInt) Serialize(table string, column string) ([]byte, error) {
val, err := ToEncryptedColumn(int(et), table, column)
if err != nil {
Expand All @@ -89,6 +103,7 @@ func (et EncryptedInt) Serialize(table string, column string) ([]byte, error) {
return json.Marshal(val)
}

// Deserialize turns a jsonb payload from CipherStash Proxy into an EncryptedInt value
func (et *EncryptedInt) Deserialize(data []byte) (EncryptedInt, error) {
var jsonData map[string]interface{}
if err := json.Unmarshal(data, &jsonData); err != nil {
Expand All @@ -106,7 +121,7 @@ func (et *EncryptedInt) Deserialize(data []byte) (EncryptedInt, error) {
return 0, fmt.Errorf("invalid format: missing 'p' field")
}

// Bool
// Serialize turns a EncryptedBool value into a jsonb payload for CipherStash Proxy
func (eb EncryptedBool) Serialize(table string, column string) ([]byte, error) {
val, err := ToEncryptedColumn(bool(eb), table, column)
if err != nil {
Expand All @@ -115,7 +130,8 @@ func (eb EncryptedBool) Serialize(table string, column string) ([]byte, error) {
return json.Marshal(val)
}

func (et *EncryptedBool) Deserialize(data []byte) (EncryptedBool, error) {
// Deserialize turns a jsonb payload from CipherStash Proxy into an EncryptedBool value
func (eb *EncryptedBool) Deserialize(data []byte) (EncryptedBool, error) {
var jsonData map[string]interface{}
if err := json.Unmarshal(data, &jsonData); err != nil {
// TODO: Check the best return values for these.
Expand All @@ -133,8 +149,7 @@ func (et *EncryptedBool) Deserialize(data []byte) (EncryptedBool, error) {
return false, fmt.Errorf("invalid format: missing 'p' field")
}

// Serialize a query

// SerializeQuery produces a jsonb payload used by EQL query functions to perform search operations like equality checks, range queries, and unique constraints.
func SerializeQuery(value any, table string, column string) ([]byte, error) {
query, err := ToEncryptedColumn(value, table, column)
if err != nil {
Expand All @@ -149,7 +164,7 @@ func SerializeQuery(value any, table string, column string) ([]byte, error) {

}

// Converts a plaintext value to a string and returns the EncryptedColumn struct to use to insert into the db.
// ToEncryptedColumn converts a plaintext value to a string, and returns the EncryptedColumn struct for inserting into a database.
func ToEncryptedColumn(value any, table string, column string) (EncryptedColumn, error) {
str, err := convertToString(value)
if err != nil {
Expand Down