Skip to content
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
80 changes: 6 additions & 74 deletions .github/workflows/go-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:

jobs:
test:
needs: get_refs
needs: get_ref
strategy:
matrix:
go-version: [1.24.x, 1.25.x]
Expand All @@ -29,7 +29,7 @@ jobs:
uses: actions/checkout@v5
with:
repository: IronCoreLabs/tenant-security-proxy
ref: ${{ needs.get_refs.outputs.tenant-security-proxy }}
ref: ${{ needs.get_ref.outputs.tsp_ref }}
path: tenant-security-proxy
token: ${{ secrets.WORKFLOW_PAT }}
- uses: actions/cache@v4
Expand Down Expand Up @@ -84,75 +84,7 @@ jobs:
with:
version: v2.5.0

# Look for a comment telling us what refs to use from the other repos we depend on.
# To add additional repositories, add them to "outputs" and to the "Setup list of required repos" step.
get_refs:
# Only run if it's on a PR.
if: github.base_ref != ''
runs-on: ubuntu-24.04
outputs:
tenant-security-proxy: ${{ steps.get_refs.outputs.tenant-security-proxy }}
steps:
- name: Setup list of required repos
run: |
echo tenant-security-proxy >> repos
- name: Get PR number
id: get_pr
run: |
PR=$(jq -r .pull_request.number "${GITHUB_EVENT_PATH}")
echo "PR is ${PR}"
# Sanity check that ${PR} is a number.
test "${PR}" -ge 0
echo "pr=${PR}" >> "$GITHUB_OUTPUT"
- name: Find Comment
uses: peter-evans/find-comment@v4
id: find_comment
with:
issue-number: ${{ steps.get_pr.outputs.pr }}
body-includes: CI_branches
- name: Parse refs
if: steps.find_comment.outputs.comment-id != 0
id: get_refs
env:
COMMENT_BODY: ${{ steps.find_comment.outputs.comment-body }}
run: |
# Extract the JSON part of the comment into a file.
echo "${COMMENT_BODY}" | tr '\n' ' ' | sed -e 's,^[^{]*,,' -e 's,[^}]*$,,' > refs.json
echo "Got JSON:"
cat refs.json && echo ""

# Sanity check that all repos in the JSON comment are ones that we know about.
jq -r 'keys[]' < refs.json > extra_repos
for REPO in $(cat repos) ; do
grep -v "^${REPO}\$" < extra_repos > temp || true
mv temp extra_repos
done
if [ -s extra_repos ] ; then
echo "Unrecognized repositories:"
cat extra_repos
exit 1
fi

# Emit an output variable for each repo.
for REPO in $(cat repos) ; do
REF=$(jq -r '.["'"${REPO}"'"]' < refs.json)
if [ "${REF}" = "null" ] ; then
REF="main"
fi
echo "${REPO}: ${REF}"
echo "${REPO}=${REF}" >> "$GITHUB_OUTPUT"
done
- name: Post a reaction (parsed your comment)
if: steps.get_refs.outcome == 'success'
uses: peter-evans/create-or-update-comment@v5
with:
issue-number: ${{ steps.get_pr.outputs.pr }}
comment-id: ${{ steps.find_comment.outputs.comment-id }}
reactions: eyes
- name: Post a reaction (unparsed comment)
if: steps.get_refs.outcome == 'failure'
uses: peter-evans/create-or-update-comment@v5
with:
issue-number: ${{ steps.get_pr.outputs.pr }}
comment-id: ${{ steps.find_comment.outputs.comment-id }}
reactions: confused
# Look for a comment telling us what TSP ref to use.
get_ref:
uses: IronCoreLabs/workflows/.github/workflows/get-tsp-ref.yaml@get-tsp-ref-v1
secrets: inherit
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## v0.6.0

- Switch `NewTenantSecurityClient` to a builder-style constructor using functional options.
- Change `TenantSecurityClient` to forbid insecure (http) connections to the TSP by default. This
can be changed with `WithAllowInsecure(true)`.

## v0.5.0

- Improve error handling when decrypting documents.
Expand Down
5 changes: 4 additions & 1 deletion examples/batch-example/batch-example.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ func main() {
}
fmt.Printf("Using tenant %s\n", tenantID)

tenantSecurityClient := tsc.NewTenantSecurityClient(apiKey, tspAddress, 0)
tenantSecurityClient, err := tsc.NewTenantSecurityClient(apiKey, tspAddress, tsc.WithAllowInsecure(true))
if err != nil {
log.Fatalf("Failed to create TSP: %v", err)
}

// Create metadata used to associate this document to a tenant, name the document, and
// identify the service or user making the call
Expand Down
7 changes: 5 additions & 2 deletions examples/logging-example/logging-example.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ func main() {
}
fmt.Printf("Using tenant %s\n", tenantID)

tenantSecurityClient := tsc.NewTenantSecurityClient(apiKey, tspAddress, 0)
tenantSecurityClient, err := tsc.NewTenantSecurityClient(apiKey, tspAddress, tsc.WithAllowInsecure(true))
if err != nil {
log.Fatalf("Failed to create TSP: %v", err)
}

// Example 1: logging a user-related event
// Create metadata about the event. This example populates all possible fields with a value,
Expand All @@ -45,7 +48,7 @@ func main() {
CustomFields: customFields}
metadata := tsc.EventMetadata{RequestMetadata: requestMetadata, TimestampMillis: time.Now().Add(-5 * time.Second)}

err := tenantSecurityClient.LogSecurityEvent(ctx, tsc.UserLoginEvent, &metadata)
err = tenantSecurityClient.LogSecurityEvent(ctx, tsc.UserLoginEvent, &metadata)
if err != nil {
log.Fatalf("Failed to log security event: %v", err)
}
Expand Down
5 changes: 4 additions & 1 deletion examples/rekey-example/rekey-example.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ func main() {
log.Fatal("Must set the API_KEY environment variable.")
}

tenantSecurityClient := tsc.NewTenantSecurityClient(apiKey, tspAddress, 0)
tenantSecurityClient, err := tsc.NewTenantSecurityClient(apiKey, tspAddress, tsc.WithAllowInsecure(true))
if err != nil {
log.Fatalf("Failed to create TSP: %v", err)
}

startingTenant := "tenant-gcp"

Expand Down
6 changes: 4 additions & 2 deletions examples/simple-example/simple-example.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ func main() {
}
fmt.Printf("Using tenant %s\n", tenantID)

tenantSecurityClient := tsc.NewTenantSecurityClient(apiKey, tspAddress, 0)

tenantSecurityClient, err := tsc.NewTenantSecurityClient(apiKey, tspAddress, tsc.WithAllowInsecure(true))
if err != nil {
log.Fatalf("Failed to create TSP: %v", err)
}
//
// Example 1: encrypting/decrypting a customer record
//
Expand Down
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ module github.com/IronCoreLabs/tenant-security-client-go
go 1.24

require (
github.com/stretchr/testify v1.7.1
google.golang.org/protobuf v1.33.0
github.com/stretchr/testify v1.11.1
google.golang.org/protobuf v1.36.10
)

require (
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
55 changes: 46 additions & 9 deletions tenant_security_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,63 @@ import (
type TenantSecurityClient struct {
tenantSecurityRequest tenantSecurityRequest
workers chan struct{}
allowInsecure bool
}

type Option func(*tscConfig)

type tscConfig struct {
parallelism int
allowInsecure bool
}

// Enables/disables allowing insecure connection to the TSP (default: false).
func WithAllowInsecure(v bool) Option {
return func(c *tscConfig) { c.allowInsecure = v }
}

// Overrides the worker parallelism. If <1, constructor will use runtime.GOMAXPROCS(0)+1.
func WithParallelism(n int) Option {
return func(c *tscConfig) { c.parallelism = n }
}

func validateTspAddress(tspAddress *url.URL, allowInsecure bool) bool {
return allowInsecure || tspAddress.Scheme == "https"
}

// NewTenantSecurityClient creates the TenantSecurityClient required for all encryption, decryption, and
// logging operations. It requires the API key used when starting the Tenant Security Proxy (TSP) as well
// as the URL of the TSP. Parallelism sets the number of CPU-bound workers which can simultaneously be
// running to encrypt and/or decrypt fields; if this is less than one, the TSC will use runtime.GOMAXPROCS(0) + 1.
func NewTenantSecurityClient(apiKey string, tspAddress *url.URL, parallelism int) *TenantSecurityClient {
req := newTenantSecurityRequest(apiKey, tspAddress)

if parallelism < 1 {
parallelism = runtime.GOMAXPROCS(0) + 1
func NewTenantSecurityClient(apiKey string, tspAddress *url.URL, opts ...Option) (*TenantSecurityClient, error) {
// default config
cfg := &tscConfig{
parallelism: runtime.GOMAXPROCS(0) + 1,
allowInsecure: false,
}
for _, o := range opts {
o(cfg)
}
workers := make(chan struct{}, parallelism)
if cfg.parallelism < 1 {
cfg.parallelism = runtime.GOMAXPROCS(0) + 1
}
if !validateTspAddress(tspAddress, cfg.allowInsecure) {
return nil, makeErrorf(errorKindLocal, "Insecure HTTP URL not allowed: `%s`", tspAddress)
}
// build request and worker pool
req := newTenantSecurityRequest(apiKey, tspAddress)
workers := make(chan struct{}, cfg.parallelism)
// Initialize the pool of worker tokens by adding that many to the channel.
for i := 0; i < parallelism; i++ {
for i := 0; i < cfg.parallelism; i++ {
workers <- struct{}{}
}

client := TenantSecurityClient{tenantSecurityRequest: *req, workers: workers}
return &client
client := &TenantSecurityClient{
tenantSecurityRequest: *req,
workers: workers,
allowInsecure: cfg.allowInsecure,
}
return client, nil
}

// encryptDocument goes through the fields of the document and encrypts each field.
Expand Down
21 changes: 19 additions & 2 deletions tenant_security_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io"
"log"
"net/url"
"os"
"testing"
Expand All @@ -15,6 +16,7 @@ import (
)

var integrationTestTSC *TenantSecurityClient
var err error

// These constants assume the TSP is running with decrypted `.env.integration` from this repo.
const (
Expand All @@ -27,7 +29,10 @@ func init() {
apiKey := os.Getenv("API_KEY")
if apiKey != "" {
url, _ := url.Parse("http://localhost:7777/")
integrationTestTSC = NewTenantSecurityClient(apiKey, url, 0)
integrationTestTSC, err = NewTenantSecurityClient(apiKey, url, WithAllowInsecure(true))
if err != nil {
log.Fatalf("Failed to create TSP: %v", err)
}
}
}

Expand All @@ -42,7 +47,7 @@ func TestEncryptConcurrency(t *testing.T) {
if err != nil {
t.Fatal(err)
}
tsc := NewTenantSecurityClient("unused", url, 2)
tsc, _ := NewTenantSecurityClient("unused", url, WithParallelism(2))

tenantID := "unused tenant"
mockDek := make([]byte, keyLen)
Expand Down Expand Up @@ -248,3 +253,15 @@ func TestLogSecurityEvent(t *testing.T) {
err := integrationTestTSC.LogSecurityEvent(context.Background(), event, &eventMetadata)
assert.Nil(t, err)
}

func urlParseUnwrap(myURL string) *url.URL {
parsed, _ := url.Parse(myURL)
return parsed
}

func TestValidateTspAddress(t *testing.T) {
assert.True(t, validateTspAddress(urlParseUnwrap("http://foo.com"), true))
assert.False(t, validateTspAddress(urlParseUnwrap("http://foo.com"), false))
assert.True(t, validateTspAddress(urlParseUnwrap("https://foo.com"), true))
assert.True(t, validateTspAddress(urlParseUnwrap("https://foo.com"), false))
}
2 changes: 1 addition & 1 deletion version.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package tsc

const Version = "0.5.1-pre"
const Version = "0.6.0-pre"
Loading