Skip to content

Latest commit

 

History

History
120 lines (92 loc) · 3.67 KB

File metadata and controls

120 lines (92 loc) · 3.67 KB

apikey — API key identifier with argon2id storage

Validates static API keys carried in a header. Three production storage backends (argon2id-hashed) plus a plaintext static map for tests.

Source: pkg/identity/apikey — registered as apikey.

When to use

  • Long-lived programmatic credentials (CI tokens, partner integrations).
  • You want hashing at rest (argon2id RFC 9106 §4 interactive params).
  • Keys can be rotated by adding a new entry alongside the old one.

Don't use for human users. Reach for oauth2 or jwt.

Configuration

identifiers:
  - name: tenant-keys
    type: apikey
    config:
      headerName: X-Api-Key            # default
      # Pick exactly ONE of static / hashed.

      # ── For tests only ─────────────────────────────────────────────
      static:
        dev-admin-key:  { subject: alice,  roles: [admin] }
        dev-viewer-key: { subject: carol,  roles: [viewer] }

      # ── Production ────────────────────────────────────────────────
      hashed:
        # 1. inline argon2id digests (small static fleets)
        entries:
          ak_alice_2026: { subject: alice, roles: [admin],
                           hash: "$argon2id$v=19$m=65536,t=3,p=2$..." }

        # 2. flat file (one digest per line, ConfigMap-friendly)
        # file: /etc/lwauth/apikeys.txt

        # 3. directory (one file per key — K8s Secret volume mount).
        #    Skips Kubernetes' "..data" symlinks automatically.
        # dir: /etc/lwauth/keys.d

Each accepted key produces Identity{Subject: <subject>, Claims: {keyId, roles, ...}}. The keyId lands in Identity.Claims["keyId"] for audit attribution.

Generate a digest — there's no CLI subcommand for this yet; apikey.HashKey is a plain Go function, call it with go run:

cat > /tmp/hash-apikey.go <<'EOF'
package main

import (
	"fmt"
	"os"

	"github.com/mikeappsec/lightweightauth/pkg/identity/apikey"
)

func main() {
	hash, err := apikey.HashKey(os.Args[1])
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	fmt.Println(hash)
}
EOF
go run /tmp/hash-apikey.go "${API_KEY}"
# → $argon2id$v=19$m=65536,t=2,p=1$...

Helm wiring

For hashed.dir mode mount a Secret volume:

# values.yaml
config:
  inline: |
    identifiers:
      - name: tenant-keys
        type: apikey
        config:
          hashed:
            dir: /etc/lwauth/keys.d
extraVolumes:
  - name: apikeys
    secret: { secretName: lwauth-apikeys }
extraVolumeMounts:
  - name: apikeys
    mountPath: /etc/lwauth/keys.d
    readOnly: true

Rotate by kubectl create secret generic lwauth-apikeys --from-file=... and the next argon2id verify picks up the new file (no Pod restart).

Worked example

GET /things HTTP/1.1
X-Api-Key: ak_alice_2026

Identity{Subject: "alice", Source: "tenant-keys", Claims: {keyId: "ak_alice_2026", roles: ["admin"]}}rbac sees roles=[admin] → allow.

Composition

  • firstMatch with jwt so service callers can present Bearer or X-Api-Key.
  • response: [jwt-issue] to mint an internal JWT downstream — keeps the raw API key out of east-west traffic.

References