Skip to content

Commit

Permalink
release v0.2.0: auto-delete unused keys
Browse files Browse the repository at this point in the history
Merge pull request #6 from ln80/auto_delete_disabled_keys
  • Loading branch information
redaLaanait authored Jun 14, 2022
2 parents bccc2ef + a551dd7 commit 53db556
Show file tree
Hide file tree
Showing 28 changed files with 1,494 additions and 137 deletions.
21 changes: 21 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Lint
on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
lint:
name: Defaults & Misspelling
runs-on: ubuntu-latest

steps:
- name: Check out code
uses: actions/checkout@v2

- name: Lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.29
args: --enable misspell
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Test & Release
name: Go Module

on:
push:
Expand All @@ -8,6 +8,7 @@ on:

jobs:
test-release:
name: Test & Release
runs-on: ubuntu-latest
services:
dynamodb:
Expand All @@ -34,7 +35,7 @@ jobs:
env:
GOPROXY: https://proxy.golang.org,direct

- name: Run Unit & Functional tests
- name: Run Unit & Integ Tests
run: |
make ci/test
env:
Expand Down
80 changes: 80 additions & 0 deletions .github/workflows/stack.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: SAM Nested Stack

on:
push:
branches: [main]
paths:
- 'stack/**'
pull_request:
branches: [main]
paths:
- 'stack/**'

jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.17

- uses: aws-actions/setup-sam@v1
with:
version: 1.40.1

- uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}

- name: Validate SAM Template
working-directory: ./stack
run: |
make validate
- name: Install Go Dependencies
working-directory: ./stack
run: |
go mod tidy
- name: Run Unit Tests
working-directory: ./stack
run: |
make unit/test
- name: Build Stack
working-directory: ./stack
run: |
make build
- name: Generate Integ Test StackName
run: echo STACK_NAME=pii-test-$(date +%s) >> $GITHUB_ENV
if: ${{ github.event_name == 'push' }}

- name: Display Integ Test StackName
run: echo ${{ env.STACK_NAME }}
if: ${{ github.event_name == 'push' }}

- name: Deploy Integ Test Stack
working-directory: ./stack
if: ${{ github.event_name == 'push' }}
run: |
make integ/deploy
- name: Run Integ Tests
working-directory: ./stack
if: ${{ github.event_name == 'push' }}
run: |
make integ/test
# in case of failure, make sure to manually run the cmd after debugging
- name: Clear Integ Tests
working-directory: ./stack
if: ${{ github.event_name == 'push' }}
run: |
make integ/clear
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
coverage.out
19 changes: 14 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,26 @@ KMS_PORT = 8090
export DYNAMODB_ENDPOINT = http://localhost:$(DYNAMODB_PORT)
export KMS_ENDPOINT = http://localhost:$(KMS_PORT)

.PHONY: lint
lint:
golangci-lint run --enable misspell

start-dynamodb:
docker run -p $(DYNAMODB_PORT):8000 amazon/dynamodb-local -jar DynamoDBLocal.jar -inMemory

test/dynamodb:
gotest -race -v -cover ./dynamodb/...

start-kms:
docker run -p $(KMS_PORT):8080 nsmithuk/local-kms

test/dynamodb:
gotest -race -v -cover ./dynamodb/... -coverprofile coverage.out

test/kms:
gotest -race -v -cover ./kms/...
gotest -race -v -cover ./kms/... -coverprofile coverage.out

ci/test:
go test -race -cover ./... -coverprofile coverage.out -covermode atomic
go test -race -cover ./... -coverprofile coverage.out -covermode atomic

test: lint ci/test

test/coverage:
go tool cover -html=coverage.out
121 changes: 60 additions & 61 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
PII
============
[![Coverage Status](https://coveralls.io/repos/github/ln80/pii/badge.svg?branch=setup_ci)](https://coveralls.io/github/ln80/pii?branch=setup_ci)
[![Coverage Status](https://coveralls.io/repos/github/ln80/pii/badge.svg?branch=setup_ci)](https://coveralls.io/github/ln80/pii)
[![GoDoc](https://godoc.org/github.com/ln80/pii?status.svg)](https://godoc.org/github.com/ln80/pii)
![ci status](https://github.com/ln80/pii/actions/workflows/pipeline.yml/badge.svg)

#### A pluggable Go library protects [Personal Identifiable Information](https://en.wikipedia.org/wiki/Personal_data) at the struct field level.
#### A pluggable Go library to protect [Personal Identifiable Information](https://en.wikipedia.org/wiki/Personal_data) at the struct field level.

#### **TLDR; PII** simplifies encryption and [cryptographic erasure](https://en.wikipedia.org/wiki/Crypto-shredding).

Expand Down Expand Up @@ -50,7 +50,7 @@ type Person struct {

`prefix` option is added to the field value to define the subject ID.

`replace` option is used to replace the cryptoghic erased field value. Otherwise, the field value will remain empty.
`replace` option is used to replace the crypto-erased field value. Otherwise, the field value will be empty.


### At the root level (ex: main func):
Expand All @@ -60,23 +60,23 @@ Initiate the `Factory` service:
func main() {
ctx := context.Background()

// builder func used by factory service to instanciate a Protector service per namespace
builder := func(namespace string) pii.Protector {

// builder func used by factory service to instantiate a Protector service per namespace
builder := func(namespace string) pii.Protector {
// engine handles encryption keys storage and lifecycle
engine := memory.NewKeyEngine()

return pii.NewProtector(namespace, enigne, func(pc *pii.ProtectorConfig) {
pc.CacheEnabled = true
pc.CacheTTL = 10 * time.Minute
pc.GracefullMode = true
})
}
return pii.NewProtector(namespace, enigne, func(pc *pii.ProtectorConfig) {
pc.CacheEnabled = true
pc.CacheTTL = 10 * time.Minute
pc.GracefulMode = true
})
}

// Factory must be injected as dependency in the functional code (ex: HTTP handlers)
f := pii.NewFactory(builder)
// Factory must be injected as dependency in the functional code (ex: HTTP handlers)
f := pii.NewFactory(builder)

// In a separated Goroutine, supervise and regulary clear resources
f.Monitor(ctx)
// In a separated Goroutine, supervise and regularly clear resources
f.Monitor(ctx)
}
```

Expand All @@ -87,40 +87,39 @@ func main() {

```go
func MakeSignupHandler(f pii.Factory, store UserStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

var per Person
err := json.NewDecoder(r.Body).Decode(&per)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

// Get the protector service for the given namespace
nspace := r.Header.Get("Tenant-ID")
prot, clear := f.Instance(nspace)

return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

var per Person
err := json.NewDecoder(r.Body).Decode(&per)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

// Get the protector service for the given namespace
nspace := r.Header.Get("Tenant-ID")
prot, clear := f.Instance(nspace)

// Optional, Force clearing cache of encryption materials (related to namespace)
defer clear()
// Optional, Force clearing cache of encryption materials (related to namespace)
defer clear()

// Encrypt Person struct which contains PII.
if err := prot.Encrypt(ctx, per); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Encrypt Person struct which contains PII.
if err := prot.Encrypt(ctx, per); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

...
...

if err := store.Save(ctx, per); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := store.Save(ctx, per); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

...

}
...
}
}

```
Expand All @@ -137,18 +136,18 @@ Allows to `Forget` a subject's `Personal data` by first disabling, then deleting
...

if err := prot.Forget(ctx, subjectID); err != nil {
return err
}
return err
}

...

if err := prot.Recover(ctx, subjectID); err != nil {
if errors.Is(err, pii.ErrCannotRecoverSubject) {
fmt.Print("Sorry it's late. Good bye forever")
}
if errors.Is(err, pii.ErrCannotRecoverSubject) {
fmt.Print("Sorry it's late. Good bye forever")
}

return err
}
return err
}

```
Forgetting a subject means we can't decrypt nor encrypt any of its old or new `Personal data`.
Expand All @@ -167,17 +166,17 @@ You can use your own implementation for each pluggin:

```go
b := func(namespace string) pii.Protector {
return pii.NewProtector(namespace, nil, func(pc *pii.ProtectorConfig) {
return pii.NewProtector(namespace, nil, func(pc *pii.ProtectorConfig) {

pc.Encrypter = MyCustomAlgorithm()

pc.Engine = MyCustomWrapper(
MyCustomKeyEngine(),
)
})
}
pc.Engine = MyCustomWrapper(
MyCustomKeyEngine(),
)
})
}

f := NewFactory(b)
f := NewFactory(b)
```


Expand Down Expand Up @@ -216,4 +215,4 @@ Please see https://pkg.go.dev/github.com/ln80/pii for detailed usage docs.

## License

Distributed under MIT License
Distributed under MIT License.
21 changes: 20 additions & 1 deletion core/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package core
import (
"context"
"errors"
"time"
)

// Errors returned by KeyEngine implementations
Expand All @@ -19,14 +20,28 @@ var (
// KeyGen presents a function used by Key engines to generate keys
type KeyGen func(ctx context.Context, namespace, keyID string) (string, error)

// KeyEngineConfig presents the basic configuration of KeyEngine
// Implementations may extend it and add specific configuration.
type KeyEngineConfig struct {
GracePeriod time.Duration
}

// NewKeyEngineConfig returns a default KeyEngineConfig
// mainly to avoid an empty GracePeriod configuration which seems to be critical.
func NewKeyEngineConfig() *KeyEngineConfig {
return &KeyEngineConfig{
GracePeriod: 7 * 24 * time.Hour,
}
}

// KeyEngine presents the service responsible for managing encryption keys.
type KeyEngine interface {

// GetKeys returns a map of keys for the given keyIDs within the given namespace.
// It doesn't return a key if it's disabled or deleted.
// The total count of the result should be less or equal to keyIDs' count.
// The returned map of keys is indexed by keyIDs.
GetKeys(ctx context.Context, namespace string, keyIDs ...string) (keys KeyMap, err error)
GetKeys(ctx context.Context, namespace string, keyIDs []string) (keys KeyMap, err error)

// GetOrCreateKeys returns the existing keys for the given keyIDs
// within the given namespace and creates new ones for the fresh new keyIDs.
Expand All @@ -44,6 +59,10 @@ type KeyEngine interface {

// DeleteKey deletes the associated key of the given keyID.
DeleteKey(ctx context.Context, namespace, keyID string) error

// DeleteUnusedKeys delete unused keys which were disabled
// for longer or equal to the configured grace period.
DeleteUnusedKeys(ctx context.Context, namespace string) error
}

// KeyEngineWrapper presents a wrapper on top of an existing Key engine.
Expand Down
Loading

0 comments on commit 53db556

Please sign in to comment.