Skip to content

Commit 03545ea

Browse files
authored
Merge pull request #6 from dcadolph/next-version
godoc examples and fuzz
2 parents 207dedb + 53aaf2f commit 03545ea

9 files changed

Lines changed: 292 additions & 0 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ All notable changes to cipher are recorded here. The format follows [Keep a Chan
66

77
### Added
88

9+
- Godoc examples for every cloud backend and for httpmw. Each package now renders runnable snippets on pkg.go.dev.
10+
- Fuzz tests for `MatchExt`, `MatchGlob`, and `MatchRegex`. Targets must never panic regardless of input.
11+
12+
## v0.2.1 (2026-06-24)
13+
14+
### Added
15+
916
- Round trip integration tests for the pgp backend using a throwaway gpg keyring and the aws kms backend using a localstack container. CI runs both on every pull request so real api drift is caught early.
1017

1118
## v0.2.0 (2026-06-24)

‎age/example_test.go‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package age_test
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
8+
"github.com/dcadolph/cipher"
9+
"github.com/dcadolph/cipher/age"
10+
)
11+
12+
// ExampleNewProvider builds a Provider from a single age recipient
13+
// and wires it into a cipher.Encoder.
14+
func ExampleNewProvider() {
15+
kp, err := age.NewProvider("age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p")
16+
if err != nil {
17+
log.Fatal(err)
18+
}
19+
enc := cipher.NewEncoder(kp)
20+
_, err = enc.Encode(context.Background(), "secrets.yaml", []byte("foo: bar\n"))
21+
if err != nil {
22+
log.Fatal(err)
23+
}
24+
}
25+
26+
// ExampleGenerateIdentity creates a throwaway age identity. Save
27+
// Secret before discarding the returned value. Recipient is safe to
28+
// publish; Secret unlocks every file encrypted to Recipient.
29+
func ExampleGenerateIdentity() {
30+
id, err := age.GenerateIdentity()
31+
if err != nil {
32+
log.Fatal(err)
33+
}
34+
fmt.Println("save this:", id.Secret) // AGE-SECRET-KEY-1...
35+
_ = id.Recipient // age1...
36+
}
37+
38+
// ExampleMustNewProvider mirrors regexp.MustCompile. Use only at
39+
// package init or in tests where construction failure is a developer
40+
// error.
41+
func ExampleMustNewProvider() {
42+
kp := age.MustNewProvider("age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p")
43+
_ = cipher.NewEncoder(kp)
44+
}

‎azkv/example_test.go‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package azkv_test
2+
3+
import (
4+
"context"
5+
"log"
6+
7+
"github.com/dcadolph/cipher"
8+
"github.com/dcadolph/cipher/azkv"
9+
)
10+
11+
// ExampleNewProvider builds a Provider that uses an Azure Key Vault
12+
// key for sops wrap. Credentials come from the azure default
13+
// credential chain (environment, managed identity, az cli).
14+
func ExampleNewProvider() {
15+
kp, err := azkv.NewProvider(
16+
"https://my-vault.vault.azure.net/keys/cipher/00112233445566778899aabbccddeeff",
17+
)
18+
if err != nil {
19+
log.Fatal(err)
20+
}
21+
enc := cipher.NewEncoder(kp)
22+
_, err = enc.Encode(context.Background(), "secrets.yaml", []byte("foo: bar\n"))
23+
if err != nil {
24+
log.Fatal(err)
25+
}
26+
}

‎gcpkms/example_test.go‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package gcpkms_test
2+
3+
import (
4+
"context"
5+
"log"
6+
7+
"github.com/dcadolph/cipher"
8+
"github.com/dcadolph/cipher/gcpkms"
9+
)
10+
11+
// ExampleNewProvider builds a Provider from one or more GCP KMS
12+
// resource ids using application default credentials.
13+
func ExampleNewProvider() {
14+
kp, err := gcpkms.NewProvider(
15+
"projects/my-project/locations/us/keyRings/cipher/cryptoKeys/prod",
16+
)
17+
if err != nil {
18+
log.Fatal(err)
19+
}
20+
enc := cipher.NewEncoder(kp)
21+
_, err = enc.Encode(context.Background(), "secrets.yaml", []byte("foo: bar\n"))
22+
if err != nil {
23+
log.Fatal(err)
24+
}
25+
}

‎httpmw/example_test.go‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package httpmw_test
2+
3+
import (
4+
"io"
5+
"log"
6+
"net/http"
7+
8+
"github.com/dcadolph/cipher"
9+
"github.com/dcadolph/cipher/age"
10+
"github.com/dcadolph/cipher/httpmw"
11+
)
12+
13+
// ExampleDecryptRequestBody wires the middleware in front of a
14+
// handler that expects plaintext. The middleware reads the encrypted
15+
// body, decrypts it with the cipher.Decoder, and the handler sees
16+
// plaintext bytes.
17+
func ExampleDecryptRequestBody() {
18+
dec := cipher.NewDecoder()
19+
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
20+
body, _ := io.ReadAll(r.Body)
21+
_, _ = w.Write(body)
22+
})
23+
mw := httpmw.DecryptRequestBody(dec, httpmw.DefaultPathFunc)
24+
http.Handle("/secrets/", mw(handler))
25+
if err := http.ListenAndServe(":8080", nil); err != nil {
26+
log.Fatal(err)
27+
}
28+
}
29+
30+
// ExampleEncryptResponseBody wraps a handler so its response is
31+
// encrypted before reaching the client. Do not wrap handlers that
32+
// rely on http.Flusher, http.Hijacker, or io.ReaderFrom because the
33+
// middleware buffers the full response.
34+
func ExampleEncryptResponseBody() {
35+
kp, _ := age.NewProvider("age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p")
36+
enc := cipher.NewEncoder(kp)
37+
handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
38+
_, _ = w.Write([]byte("api_key: secret\n"))
39+
})
40+
mw := httpmw.EncryptResponseBody(enc, httpmw.DefaultPathFunc)
41+
http.Handle("/secrets/", mw(handler))
42+
if err := http.ListenAndServe(":8080", nil); err != nil {
43+
log.Fatal(err)
44+
}
45+
}

‎kms/example_test.go‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package kms_test
2+
3+
import (
4+
"context"
5+
"log"
6+
7+
"github.com/dcadolph/cipher"
8+
"github.com/dcadolph/cipher/kms"
9+
)
10+
11+
// ExampleNewProvider builds a kms Provider from one or more ARNs
12+
// using the default aws credential chain.
13+
func ExampleNewProvider() {
14+
kp, err := kms.NewProvider("arn:aws:kms:us-east-1:111122223333:key/abcd1234-ef56-7890-abcd-ef1234567890")
15+
if err != nil {
16+
log.Fatal(err)
17+
}
18+
enc := cipher.NewEncoder(kp)
19+
_, err = enc.Encode(context.Background(), "secrets.yaml", []byte("foo: bar\n"))
20+
if err != nil {
21+
log.Fatal(err)
22+
}
23+
}
24+
25+
// ExampleNewProviderWith adds an encryption context and an aws
26+
// profile. The same encryption context must be available at
27+
// decryption time.
28+
func ExampleNewProviderWith() {
29+
opts := kms.ProviderOptions{
30+
EncryptionContext: map[string]string{"env": "prod"},
31+
Profile: "cipher-ci",
32+
}
33+
kp, err := kms.NewProviderWith(
34+
opts,
35+
"arn:aws:kms:us-east-1:111122223333:key/abcd1234-ef56-7890-abcd-ef1234567890",
36+
)
37+
if err != nil {
38+
log.Fatal(err)
39+
}
40+
_ = cipher.NewEncoder(kp)
41+
}

‎matcher_fuzz_test.go‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package cipher_test
2+
3+
import (
4+
"regexp"
5+
"strings"
6+
"testing"
7+
8+
"github.com/dcadolph/cipher"
9+
)
10+
11+
// FuzzMatchExt feeds arbitrary extension lists and paths into the
12+
// MatchExt builder. Any input that compiles must not panic when
13+
// Match is called.
14+
func FuzzMatchExt(f *testing.F) {
15+
f.Add("yaml,yml,json", "secrets/prod.yaml")
16+
f.Add("", "noext")
17+
f.Add(".YAML", "MIXED.yaml")
18+
f.Fuzz(func(t *testing.T, exts, path string) {
19+
parts := strings.Split(exts, ",")
20+
m := cipher.MatchExt(parts...)
21+
_ = m.Match(path)
22+
})
23+
}
24+
25+
// FuzzMatchGlob feeds arbitrary glob patterns and paths. Patterns
26+
// that fail to compile return an error and are skipped; patterns
27+
// that compile must never panic on Match.
28+
func FuzzMatchGlob(f *testing.F) {
29+
f.Add("**/*.yaml", "a/b/c.yaml")
30+
f.Add("secrets/*", "secrets/prod")
31+
f.Add("[", "anything")
32+
f.Fuzz(func(t *testing.T, pattern, path string) {
33+
m, err := cipher.MatchGlob(pattern)
34+
if err != nil {
35+
return
36+
}
37+
_ = m.Match(path)
38+
})
39+
}
40+
41+
// FuzzMatchRegex feeds arbitrary regexes and paths. Patterns that
42+
// fail to compile are skipped. Compiled patterns must never panic on
43+
// Match.
44+
func FuzzMatchRegex(f *testing.F) {
45+
f.Add(`\.yaml$`, "x.yaml")
46+
f.Add(`^secrets/`, "secrets/db")
47+
f.Add(`(?P<g>foo)`, "foo")
48+
f.Fuzz(func(t *testing.T, pattern, path string) {
49+
re, err := regexp.Compile(pattern)
50+
if err != nil {
51+
return
52+
}
53+
m := cipher.MatchRegex(re)
54+
_ = m.Match(path)
55+
})
56+
}

‎pgp/example_test.go‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package pgp_test
2+
3+
import (
4+
"context"
5+
"log"
6+
7+
"github.com/dcadolph/cipher"
8+
"github.com/dcadolph/cipher/pgp"
9+
)
10+
11+
// ExampleNewProvider builds a Provider from one or more gpg key
12+
// fingerprints. The local gpg binary must hold the matching public
13+
// keys in its keyring at encrypt time, and the private keys at
14+
// decrypt time.
15+
func ExampleNewProvider() {
16+
kp, err := pgp.NewProvider("AAAA1111BBBB2222CCCC3333DDDD4444EEEE5555")
17+
if err != nil {
18+
log.Fatal(err)
19+
}
20+
enc := cipher.NewEncoder(kp)
21+
_, err = enc.Encode(context.Background(), "secrets.yaml", []byte("foo: bar\n"))
22+
if err != nil {
23+
log.Fatal(err)
24+
}
25+
}

‎vault/example_test.go‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package vault_test
2+
3+
import (
4+
"context"
5+
"log"
6+
7+
"github.com/dcadolph/cipher"
8+
"github.com/dcadolph/cipher/vault"
9+
)
10+
11+
// ExampleNewProvider builds a Provider that talks to a vault transit
12+
// key. Vault address and token come from VAULT_ADDR and VAULT_TOKEN.
13+
func ExampleNewProvider() {
14+
kp, err := vault.NewProvider("https://vault.example.com/v1/transit/keys/cipher")
15+
if err != nil {
16+
log.Fatal(err)
17+
}
18+
enc := cipher.NewEncoder(kp)
19+
_, err = enc.Encode(context.Background(), "secrets.yaml", []byte("foo: bar\n"))
20+
if err != nil {
21+
log.Fatal(err)
22+
}
23+
}

0 commit comments

Comments
 (0)