Full Code of cloudflare/lockbox for AI

trunk b7bc038eb7db cached
46 files
128.7 KB
49.5k tokens
127 symbols
1 requests
Download .txt
Repository: cloudflare/lockbox
Branch: trunk
Commit: b7bc038eb7db
Files: 46
Total size: 128.7 KB

Directory structure:
gitextract_0jzthxzx/

├── .dockerignore
├── .github/
│   └── workflows/
│       ├── docker.yaml
│       ├── semgrep.yml
│       └── tests.yaml
├── .gitignore
├── LICENSE
├── Makefile
├── README.org
├── cmd/
│   ├── lockbox-controller/
│   │   ├── Dockerfile
│   │   ├── keypair.go
│   │   └── main.go
│   ├── lockbox-keypair/
│   │   └── main.go
│   └── locket/
│       └── main.go
├── deployment/
│   ├── crds/
│   │   └── lockbox.k8s.cloudflare.com_lockboxes.yaml
│   ├── manifests/
│   │   ├── deployment-lockbox.yaml
│   │   ├── namespace-lockbox.yaml
│   │   ├── service-lockbox.yaml
│   │   └── serviceaccount-lockbox.yaml
│   └── rbac/
│       ├── proxier.yaml
│       ├── role-binding.yaml
│       └── role.yaml
├── go.mod
├── go.sum
├── pkg/
│   ├── apis/
│   │   └── lockbox.k8s.cloudflare.com/
│   │       └── v1/
│   │           ├── groupversion_info.go
│   │           ├── lockbox.go
│   │           ├── lockbox_test.go
│   │           ├── types.go
│   │           └── zz_generated.deepcopy.go
│   ├── flagvar/
│   │   ├── enum.go
│   │   ├── enum_test.go
│   │   ├── file.go
│   │   ├── file_test.go
│   │   ├── tcp_addr.go
│   │   ├── tcp_addr_test.go
│   │   └── testdata/
│   │       └── file
│   ├── lockbox-controller/
│   │   ├── secretreconciler.go
│   │   ├── secretreconciler_suite_test.go
│   │   └── secretreconciler_test.go
│   ├── lockbox-server/
│   │   └── serve.go
│   ├── statemetrics/
│   │   ├── collector.go
│   │   ├── handler.go
│   │   ├── handler_test.go
│   │   ├── labels.go
│   │   └── labels_test.go
│   └── util/
│       └── conditions/
│           └── conditions.go
└── tools/
    └── tools.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
bin/


================================================
FILE: .github/workflows/docker.yaml
================================================
name: Docker
on:
  - pull_request
  - push
jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-qemu-action@v3
      - uses: docker/metadata-action@v5
        id: docker-meta
        with:
          images: cloudflare/lockbox
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        if: ${{ startsWith(github.ref, 'refs/tags/v') }}
        with:
          username: ${{ secrets.DOCKER_HUB_USERNAME }}
          password: ${{ secrets.DOCKER_HUB_TOKEN }}
      - uses: docker/build-push-action@v5
        with:
          file: ./cmd/lockbox-controller/Dockerfile
          platforms: linux/amd64, linux/arm64
          tags: ${{ steps.docker-meta.outputs.tags }}
          push: ${{ startsWith(github.ref, 'refs/tags/v') }}


================================================
FILE: .github/workflows/semgrep.yml
================================================
on:
  pull_request: {}
  workflow_dispatch: {}
  push:
    branches:
      - trunk
  schedule:
    - cron: "0 0 * * *"
name: Semgrep config
jobs:
  semgrep:
    name: semgrep/ci
    runs-on: ubuntu-latest
    env:
      SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
      SEMGREP_URL: https://cloudflare.semgrep.dev
      SEMGREP_APP_URL: https://cloudflare.semgrep.dev
      SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version
    container:
      image: semgrep/semgrep
    steps:
      - uses: actions/checkout@v4
      - run: semgrep ci


================================================
FILE: .github/workflows/tests.yaml
================================================
name: Test
on:
  - pull_request
  - push
jobs:
  unit:
    runs-on: ubuntu-latest
    name: "Go ${{ matrix.go }} Test"
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v4
        with:
          go-version: "stable"
      - run: make test
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v4
        with:
          go-version: "stable"
      - uses: dominikh/staticcheck-action@v1
        with:
          build-tags: suite
          install-go: false
  integration:
    needs:
      - unit
      - lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v4
        with:
          go-version: "stable"
      - run: |
          go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest
          source <(setup-envtest use -p env)
          go test ./... -tags suite


================================================
FILE: .gitignore
================================================
## Go.gitignore ##

# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Vendor directory
/vendor

## nix.gitignore ##

/result*
/bin/


================================================
FILE: LICENSE
================================================
BSD 3-Clause License

Copyright (c) 2020, Cloudflare, Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
   contributors may be used to endorse or promote products derived from
   this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.



================================================
FILE: Makefile
================================================
.DEFAULT_GOAL := binaries

KERNEL := $(shell uname -s)
GOTESTSUM := $(shell command -v gotestsum 2> /dev/null)

DIB ?= docker
IMAGE_ROOT ?= localhost/lockbox
IMAGE_VERSION ?= $(shell git log -1 --pretty=format:%cd-%h --date short HEAD)
VERSION := $(shell git describe --tags --always --dirty=-dev)
# Build docker images for the native arch, but allow overriding in the environment for local development
PLATFORM ?= local

# Bind mount $SSL_CERT_FILE (or default) to build container if the file exists.
SSL_CERT_FILE ?= /etc/ssl/certs/ca-certificates.crt
ifneq (,$(wildcard ${SSL_CERT_FILE}))
SECRETS = --secret id=certificates,src=${SSL_CERT_FILE}
endif

# When compiling for Linux enable Security's recommend hardening to satisfy `checksec' checks.
# Unfortunately, most of these flags aren't portable to other operating systems.
ifeq (${KERNEL},Linux)
	CGO_ENABLED ?= 1
	CPPFLAGS ?= -D_FORTIFY_SOURCE=2 -fstack-protector-all
	CFLAGS ?= -O2 -pipe -fno-plt
	CXXFLAGS ?= -O2 -pipe -fno-plt
	LDFLAGS ?= -Wl,-O1,-sort-common,-as-needed,-z,relro,-z,now
	GO_LDFLAGS ?= -linkmode=external
	GOFLAGS ?= -buildmode=pie
endif

GO_LDFLAGS += -w -s -X main.version=${VERSION}
GOFLAGS += -v

export CGO_ENABLED
export CGO_CPPFLAGS ?= ${CPPFLAGS}
export CGO_CFLAGS ?= ${CFLAGS}
export CGO_CXXFLAGS ?= ${CXXFLAGS}
export CGO_LDFLAGS ?= ${LDFLAGS}

CMDS := $(shell find cmd -mindepth 1 -maxdepth 1 -type d | awk -F '/' '{ print $$NF }' )
IMAGES := $(shell find cmd -mindepth 1 -type f -name Dockerfile | awk -F '/' '{ print $$2 }')

define make-go-target
.PHONY: bin/$1
bin/$1:
	go build ${GOFLAGS} -o $$@ -ldflags "${GO_LDFLAGS}" ./cmd/$1
endef

define make-dib-targets
.PHONY: images/$1
images/$1:
	${DIB} buildx build --platform "$(PLATFORM)" ${SECRETS} -f cmd/$1/Dockerfile -t "${IMAGE_ROOT}/$1:${IMAGE_VERSION}" .

.PHONY: push/images/$1
push/images/$1:
	${DIB} push "${IMAGE_ROOT}/$1:${IMAGE_VERSION}"
endef

$(foreach element,$(CMDS), $(eval $(call make-go-target,$(element))))
$(foreach element,$(IMAGES), $(eval $(call make-dib-targets,$(element))))

.PHONY: binaries
binaries: $(CMDS:%=bin/%)

.PHONY: images
images: $(IMAGES:%=images/%)

.PHONY: push-images
push-images: $(IMAGES:%=push/images/%)

.PHONY: clean
clean:
	rm -rf bin

.PHONY: test
test:
ifdef GOTESTSUM
	"${GOTESTSUM}" -- -count 1 ./...
else
	go test -cover -count 1 ./...
endif

.PHONY: lint
lint:
	staticcheck -tags suite ./...

.PHONY: controller-gen
controller-gen:
	go install sigs.k8s.io/controller-tools/cmd/controller-gen

.PHONY: go-generate
go-generate: controller-gen
	go generate -v ./...


================================================
FILE: README.org
================================================
#+TITLE: Lockbox

[[https://pkg.go.dev/github.com/cloudflare/lockbox][https://pkg.go.dev/badge/github.com/cloudflare/lockbox.png]]

Lockbox is a secure way to store Kubernetes Secrets offline. Secrets are asymmetrically encrypted, and can only be decrypted by the Lockbox Kubernetes controller. A companion CLI tool, =locket=, makes encrypting secrets a one-step process.

** Features
+ Secure encryption using modern cryptography. Uses Salsa20, Poly1305, and Curve25519.
+ Secrets are locked to specific namespaces.
+ All Kubernetes Secret types are supported.
+ Plays nicely with Secrets created by other controllers.
+ Continuously reconciles child resources.

** Example Usage
Create a native Secret, but pass =--dry-run= to avoid submitting to the API.

#+begin_example
$ kubectl create secret generic mysecret --namespace default \
  --from-literal=foo=bar --dry-run -o yaml > mysecret.yaml
#+end_example

Then, use locket to encrypt the secret.

#+begin_example
$ locket -f mysecret.yaml > mylockbox.yaml
#+end_example

Submit the lockbox to the API.

#+begin_example
$ kubectl create -f mylockbox.yaml
#+end_example

Remove the unencrypted secret.

#+begin_example
$ rm mysecret.yaml
#+end_example


================================================
FILE: cmd/lockbox-controller/Dockerfile
================================================
FROM docker.io/library/golang:1.21.5-bookworm AS builder
WORKDIR /go/src/app
ADD . /go/src/app

RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    --mount=type=secret,id=certificates,target=/etc/ssl/certs/ca-certificates.crt \
    make bin/lockbox-controller


FROM gcr.io/distroless/base-nossl-debian12:nonroot
COPY --from=builder /go/src/app/bin/lockbox-controller /bin
ENTRYPOINT ["/bin/lockbox-controller"]


================================================
FILE: cmd/lockbox-controller/keypair.go
================================================
package main

import (
	"fmt"
	"io"

	"github.com/kevinburke/nacl"
	"sigs.k8s.io/yaml"
)

type kp struct {
	Private []byte `json:"private"`
	Public  []byte `json:"public"`
}

// KeyPairFromYAMLOrJSON loads a public/private NaCL keypair from a YAML or JSON file.
func KeyPairFromYAMLOrJSON(r io.Reader) (pub, pri nacl.Key, err error) {
	data, err := io.ReadAll(r)
	if err != nil {
		return
	}

	keypair := kp{}
	err = yaml.Unmarshal(data, &keypair, yaml.DisallowUnknownFields)
	if err != nil {
		return
	}

	if len(keypair.Private) != 32 {
		err = fmt.Errorf("incorrect private key length: %d, should be 32", len(keypair.Private))
		return
	}
	if len(keypair.Public) != 32 {
		err = fmt.Errorf("incorrect public key length: %d, should be 32", len(keypair.Public))
		return
	}

	pub = new([nacl.KeySize]byte)
	pri = new([nacl.KeySize]byte)

	copy(pri[:], keypair.Private)
	copy(pub[:], keypair.Public)
	return
}


================================================
FILE: cmd/lockbox-controller/main.go
================================================
package main

import (
	"context"
	"flag"
	"fmt"
	"net"
	"net/http"
	"os"
	"runtime"
	"time"

	lockboxv1 "github.com/cloudflare/lockbox/pkg/apis/lockbox.k8s.cloudflare.com/v1"
	"github.com/cloudflare/lockbox/pkg/flagvar"
	lockboxcontroller "github.com/cloudflare/lockbox/pkg/lockbox-controller"
	server "github.com/cloudflare/lockbox/pkg/lockbox-server"
	"github.com/cloudflare/lockbox/pkg/statemetrics"
	"github.com/go-logr/zerologr"
	"github.com/kevinburke/nacl"
	"github.com/rs/zerolog"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/client-go/kubernetes/scheme"
	"sigs.k8s.io/controller-runtime/pkg/cache"
	"sigs.k8s.io/controller-runtime/pkg/client/config"
	"sigs.k8s.io/controller-runtime/pkg/controller"
	"sigs.k8s.io/controller-runtime/pkg/handler"
	logf "sigs.k8s.io/controller-runtime/pkg/log"
	"sigs.k8s.io/controller-runtime/pkg/manager"
	"sigs.k8s.io/controller-runtime/pkg/manager/signals"
	"sigs.k8s.io/controller-runtime/pkg/metrics"
	metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"
	"sigs.k8s.io/controller-runtime/pkg/source"
)

var (
	pubKey, priKey nacl.Key
	version        = "dev"
	syncPeriod     = 1 * time.Hour
	keypairPath    = flagvar.File{Value: "/etc/lockbox/keypair.yaml"}
	metricsAddr    = flagvar.TCPAddr{Text: ":8080"}
	httpAddr       = flagvar.TCPAddr{Text: ":8081"}
)

func main() {
	flag.Var(&keypairPath, "keypair", fmt.Sprintf("public/private 32 byte keypairs (%s)", keypairPath.Help()))
	flag.Var(&metricsAddr, "metrics-addr", fmt.Sprintf("bind for HTTP metrics (%s)", metricsAddr.Help()))
	flag.Var(&httpAddr, "http-addr", fmt.Sprintf("bind for HTTP server (%s)", httpAddr.Help()))
	flag.DurationVar(&syncPeriod, "sync-period", syncPeriod, "controller sync period")
	flag.String("v", "", "log level for V logs")
	flag.Parse()

	zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs
	zerologr.NameFieldName = "logger"
	zerologr.NameSeparator = "/"

	zl := zerolog.New(os.Stderr).With().Caller().Timestamp().Logger()
	logf.SetLogger(zerologr.New(&zl))
	logger := zl.With().Str("name", "main").Logger()

	keypair, err := os.Open(keypairPath.Value)
	if err != nil {
		logger.Fatal().Err(err).Str("path", keypairPath.Value).Msg("unable to open keypair")
		os.Exit(1)
	}
	pubKey, priKey, err = KeyPairFromYAMLOrJSON(keypair)
	if err != nil {
		logger.Fatal().Err(err).Str("path", keypairPath.Value).Msg("unable to parse keypair")
		os.Exit(1)
	}
	keypair.Close()

	err = lockboxv1.AddToScheme(scheme.Scheme)
	if err != nil {
		logger.Fatal().Err(err).Msg("unable to add lockbox schemes")
		os.Exit(1)
	}

	cfg, err := config.GetConfig()
	cfg.UserAgent = fmt.Sprintf("%s/%s (%s/%s)", os.Args[0], version, runtime.GOOS, runtime.GOARCH)

	if err != nil {
		logger.Fatal().Err(err).Msg("unable to get kubeconfig")
		os.Exit(1)
	}

	mgr, err := manager.New(cfg, manager.Options{
		Metrics: metricsserver.Options{
			BindAddress: metricsAddr.Text,
		},
		Cache: cache.Options{
			SyncPeriod: &syncPeriod,
		},
		Scheme: scheme.Scheme,
	})
	if err != nil {
		logger.Fatal().Err(err).Msg("unable to create controller manager")
		os.Exit(1)
	}

	recorder := mgr.GetEventRecorderFor("lockbox")
	client := mgr.GetClient()

	sr := lockboxcontroller.NewSecretReconciler(pubKey, priKey, lockboxcontroller.WithRecorder(recorder), lockboxcontroller.WithClient(client))

	info := statemetrics.NewKubernetesVec(statemetrics.KubernetesOpts{
		Name: "kube_lockbox_info",
		Help: "Information about Lockbox",
	}, []string{"namespace", "lockbox"})
	created := statemetrics.NewKubernetesVec(statemetrics.KubernetesOpts{
		Name: "kube_lockbox_created",
		Help: "Unix creation timestamp",
	}, []string{"namespace", "lockbox"})
	resourceVersion := statemetrics.NewKubernetesVec(statemetrics.KubernetesOpts{
		Name: "kube_lockbox_resource_version",
		Help: "Resource version representing a specific version of a Lockbox",
	}, []string{"namespace", "lockbox", "resource_version"})
	lbType := statemetrics.NewKubernetesVec(statemetrics.KubernetesOpts{
		Name: "kube_lockbox_type",
		Help: "Lockbox secret type",
	}, []string{"namespace", "lockbox", "type"})
	peerKey := statemetrics.NewKubernetesVec(statemetrics.KubernetesOpts{
		Name: "kube_lockbox_peer",
		Help: "Lockbox peer key",
	}, []string{"namespace", "lockbox", "peer"})
	labels := statemetrics.NewLabelsVec(statemetrics.KubernetesOpts{
		Name: "kube_lockbox_labels",
		Help: "Kubernetes labels converted to Prometheus labels",
	})
	metrics.Registry.MustRegister(info, created, resourceVersion, lbType, labels, peerKey)

	mh := statemetrics.NewStateMetricProxy(
		&handler.EnqueueRequestForObject{},
		info, created, resourceVersion,
		lbType, peerKey, labels,
	)

	c, err := controller.New("lockbox-controller", mgr, controller.Options{
		Reconciler: reconcile.AsReconciler(mgr.GetClient(), sr),
	})

	if err != nil {
		logger.Fatal().Err(err).Msg("unable to create controller")
		os.Exit(1)
	}

	if err := c.Watch(source.Kind(mgr.GetCache(), &lockboxv1.Lockbox{}), mh); err != nil {
		logger.Fatal().Err(err).Msg("unable to watch Lockbox resources")
		os.Exit(1)
	}

	if err := c.Watch(source.Kind(mgr.GetCache(), &corev1.Secret{}), handler.EnqueueRequestForOwner(scheme.Scheme, mgr.GetRESTMapper(), &lockboxv1.Lockbox{}, handler.OnlyControllerOwner())); err != nil {
		logger.Fatal().Err(err).Msg("unable to watch Secret resources")
		os.Exit(1)
	}

	// TODO(terin): make server implement Runnable
	if err := mgr.Add(manager.RunnableFunc(func(ctx context.Context) error {
		mux := http.NewServeMux()
		mux.Handle("/v1/public", server.PublicKey(pubKey))

		ln, err := net.Listen("tcp", httpAddr.Text)
		if err != nil {
			return err
		}

		// sig.kubernetes.io/controller-runtime/pkg/internal/httpserver
		s := http.Server{
			Handler:           mux,
			MaxHeaderBytes:    1 << 20,
			IdleTimeout:       90 * time.Second,
			ReadHeaderTimeout: 32 * time.Second,
		}

		idleConnsClosed := make(chan struct{})
		go func() {
			<-ctx.Done()

			if err := s.Shutdown(context.Background()); err != nil {
				logger.Err(err).Send()
			}
			close(idleConnsClosed)
		}()

		if err := s.Serve(ln); err != nil && err != http.ErrServerClosed {
			return err
		}

		<-idleConnsClosed
		return nil
	})); err != nil {
		logger.Fatal().Err(err).Msg("unable to add server runnable")
	}

	if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
		logger.Fatal().Err(err).Send()
	}
}


================================================
FILE: cmd/lockbox-keypair/main.go
================================================
package main

import (
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"os"

	"github.com/kevinburke/nacl/box"
)

func main() {
	lockboxPubKey, lockboxPriKey, err := box.GenerateKey(rand.Reader)
	if err != nil {
		panic(err)
	}

	pub64 := base64.StdEncoding.EncodeToString(lockboxPubKey[:])
	pri64 := base64.StdEncoding.EncodeToString(lockboxPriKey[:])

	fmt.Fprintf(os.Stdout, "public:  %s\nprivate: %s\n", pub64, pri64)
}


================================================
FILE: cmd/locket/main.go
================================================
package main

import (
	"context"
	"crypto/rand"
	"flag"
	"fmt"
	"io"
	"os"
	gruntime "runtime"
	"time"

	lockboxv1 "github.com/cloudflare/lockbox/pkg/apis/lockbox.k8s.cloudflare.com/v1"
	"github.com/cloudflare/lockbox/pkg/flagvar"
	"github.com/go-logr/zerologr"
	"github.com/kevinburke/nacl"
	"github.com/kevinburke/nacl/box"
	"github.com/rs/zerolog"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/runtime"
	runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/tools/clientcmd"
	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
	logf "sigs.k8s.io/controller-runtime/pkg/log"
)

var (
	input        = flagvar.File{}
	kubeconfig   = flagvar.File{}
	output       = flagvar.Enum{Choices: []string{"json", "yaml"}, Value: "yaml"}
	version      = "dev"
	printVersion bool
	peerHex      string
	masterURL    string
	lockboxNS    string
	lockboxSvc   string
)

func main() {
	flag.Var(&input, "f", fmt.Sprintf("input file (%s)", input.Help()))
	flag.Var(&output, "o", fmt.Sprintf("output format (%s)", output.Help()))
	flag.Var(&kubeconfig, "kubeconfig", fmt.Sprintf("path to kubeconfig. (%s)", kubeconfig.Help()))
	flag.StringVar(&peerHex, "peer-hex", "", "peer public key (32-bit hex)")
	flag.StringVar(&masterURL, "master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.")
	flag.StringVar(&lockboxNS, "lockbox-namespace", "lockbox", "namespace of the lockbox controller")
	flag.StringVar(&lockboxSvc, "lockbox-service", "lockbox", "name of the lockbox service")
	flag.BoolVar(&printVersion, "version", false, "print version")
	flag.String("v", "", "log level for V logs")
	flag.Parse()

	ctx := context.Background()

	if printVersion {
		fmt.Printf("locket: %s\n", version)
		os.Exit(0)
	}

	zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs
	zerologr.NameFieldName = "logger"
	zerologr.NameSeparator = "/"

	zl := zerolog.New(os.Stderr).With().Caller().Timestamp().Logger()
	logf.SetLogger(zerologr.New(&zl))
	logger := zl.With().Str("name", "main").Logger()

	err := lockboxv1.AddToScheme(scheme.Scheme)
	if err != nil {
		logger.Fatal().Err(err).Msg("unable to add lockbox schemes")
		os.Exit(1)
	}

	var r io.Reader
	if input.String() == "" {
		r = os.Stdin
	} else {
		r, err = os.Open(input.String())
		if err != nil {
			logger.Fatal().Err(err).Msg("unable to open secret file")
			os.Exit(1)
		}
	}

	w := os.Stdout

	cfg := GetConfig()

	cf := runtimeserializer.NewCodecFactory(scheme.Scheme)

	ib, err := io.ReadAll(r)
	if err != nil {
		logger.Fatal().Err(err).Msg("unable to read secret file")
		os.Exit(1)
	}
	var secret corev1.Secret
	if err = runtime.DecodeInto(cf.UniversalDecoder(), ib, &secret); err != nil {
		logger.Fatal().Err(err).Msg("unable to decode secret file")
		os.Exit(1)
	}

	pubKey, priKey, err := box.GenerateKey(rand.Reader)
	if err != nil {
		logger.Fatal().Err(err).Msg("could not generate key")
		os.Exit(1)
	}

	var peerKey nacl.Key
	switch peerHex {
	case "":
		ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
		defer cancel()

		cc, err := cfg.ClientConfig()
		if err != nil {
			logger.Fatal().Err(err).Msg("unable to create API client configuration")
			os.Exit(1)
		}

		cc.UserAgent = fmt.Sprintf("%s/%s (%s/%s)", os.Args[0], version, gruntime.GOOS, gruntime.GOARCH)

		client, err := kubernetes.NewForConfig(cc)
		if err != nil {
			logger.Fatal().Err(err).Msg("unable to create API client")
			os.Exit(1)
		}

		b, err := GetRemotePublicKey(ctx, client, lockboxNS, lockboxSvc)
		if err != nil {
			logger.Fatal().Err(err).Msg("unable to fetch public key")
			os.Exit(1)
		}
		if len(b) != 32 {
			err = fmt.Errorf("incorrect peer key length: %d, should be 32", len(b))
			logger.Fatal().Err(err).Msg("unable to fetch peer key")
			return
		}

		peerKey = new([nacl.KeySize]byte)
		copy(peerKey[:], b)
	default:
		peerKey, err = nacl.Load(peerHex)
		if err != nil {
			logger.Fatal().Err(err).Msg("could not load --peer-hex")
			os.Exit(1)
		}
	}

	namespace := secret.Namespace
	if namespace == "" {
		namespace, _, _ = cfg.Namespace()
	}

	b := lockboxv1.NewFromSecret(secret, namespace, peerKey, pubKey, priKey)

	var ct string
	switch output.String() {
	case "yaml":
		ct = "application/yaml"
	case "json":
		ct = "application/json"
	}

	info, ok := runtime.SerializerInfoForMediaType(cf.SupportedMediaTypes(), ct)
	if !ok {
		logger.Fatal().Str("content-type", ct).Msg("can't serialize to content-type")
		os.Exit(1)
	}
	serial := info.Serializer
	if info.PrettySerializer != nil {
		serial = info.PrettySerializer
	}
	enc := cf.EncoderForVersion(serial, lockboxv1.GroupVersion)

	ob, err := runtime.Encode(enc, b)
	if err != nil {
		logger.Fatal().Err(err).Msg("unable to encode Lockbox")
		os.Exit(1)
	}

	if _, err := w.Write(ob); err != nil {
		logger.Fatal().Err(err).Send()
	}
	if _, err := w.WriteString("\n"); err != nil {
		logger.Fatal().Err(err).Send()
	}
}

func GetConfig() clientcmd.ClientConfig {
	loader := clientcmd.NewDefaultClientConfigLoadingRules()
	overrides := clientcmd.ConfigOverrides{
		ClusterInfo: clientcmdapi.Cluster{
			Server: masterURL,
		},
	}
	loader.ExplicitPath = kubeconfig.String()
	return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loader, &overrides)
}

func GetRemotePublicKey(ctx context.Context, c kubernetes.Interface, ns, svc string) ([]byte, error) {
	return c.CoreV1().Services(ns).ProxyGet("http", svc, "", "/v1/public", nil).DoRaw(ctx)
}


================================================
FILE: deployment/crds/lockbox.k8s.cloudflare.com_lockboxes.yaml
================================================
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  annotations:
    controller-gen.kubebuilder.io/version: v0.13.0
  name: lockboxes.lockbox.k8s.cloudflare.com
spec:
  group: lockbox.k8s.cloudflare.com
  names:
    kind: Lockbox
    listKind: LockboxList
    plural: lockboxes
    singular: lockbox
  scope: Namespaced
  versions:
  - additionalPrinterColumns:
    - jsonPath: .spec.template.type
      name: SecretType
      type: string
    - jsonPath: .spec.peer
      name: Peer
      type: string
    name: v1
    schema:
      openAPIV3Schema:
        description: Lockbox is a struct wrapping the LockboxSpec in standard API
          server metadata fields.
        properties:
          apiVersion:
            description: 'APIVersion defines the versioned schema of this representation
              of an object. Servers should convert recognized schemas to the latest
              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
            type: string
          kind:
            description: 'Kind is a string value representing the REST resource this
              object represents. Servers may infer this from the endpoint the client
              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
            type: string
          metadata:
            type: object
          spec:
            description: Desired state of the Lockbox resource.
            properties:
              data:
                additionalProperties:
                  format: byte
                  type: string
                description: Data contains the secret data, encrypted to the Peer's
                  public key. Each key in the data map must consist of alphanumeric
                  characters, '-', '_', or '.'.
                type: object
              namespace:
                description: Namespace stores an encrypted copy of which namespace
                  this Lockbox is locked for, ensuring it cannot be deployed to another
                  namespace under an attacker's control.
                format: byte
                type: string
              peer:
                description: Peer stores the public key that can unlock this Lockbox.
                format: byte
                type: string
              sender:
                description: Sender stores the public key used to lock this Lockbox.
                format: byte
                type: string
              template:
                description: Template defines the structure of the Secret that will
                  be created from this Lockbox.
                properties:
                  metadata:
                    properties:
                      annotations:
                        additionalProperties:
                          type: string
                        description: 'Annotations is an unstructured key value map
                          stored with a resource that may be set by external tools
                          to store and retrieve arbitrary metadata. They are not queryable
                          and should be preserved when modifying objects. More info:
                          http://kubernetes.io/docs/user-guide/annotations'
                        type: object
                      labels:
                        additionalProperties:
                          type: string
                        description: 'Map of string keys and values that can be used
                          to organize and categorize (scope and select) objects. May
                          match selectors of replication controllers and services.
                          More info: http://kubernetes.io/docs/user-guide/labels'
                        type: object
                    type: object
                  type:
                    description: Type is used to facilitate programmatic handling
                      of secret data.
                    type: string
                type: object
            required:
            - data
            - namespace
            - peer
            - sender
            type: object
          status:
            description: Status of the Lockbox. This is set and managed automatically.
            properties:
              conditions:
                description: List of status conditions to indicate the status of a
                  Lockbox.
                items:
                  description: Condition contains condition information for a Lockbox.
                  properties:
                    lastTransitionTime:
                      description: LastTransitionTime marks when the condition last
                        transitioned from one status to another. This should be when
                        the underlying condition changed. If that is not known, then
                        using the time when the API field changed is acceptable.
                      format: date-time
                      type: string
                    message:
                      description: A message is the human readable message indicating
                        details about the transition. The field may be empty.
                      type: string
                    reason:
                      description: The reason for the condition's last transition
                        in CamelCase.
                      type: string
                    severity:
                      description: Severity provides explicit classification of Reason
                        code, so that users or machines can immediately understand
                        the current situation and act accordingly. The Severity field
                        MUST be set only when Status=False.
                      enum:
                      - Error
                      - Warning
                      - Info
                      type: string
                    status:
                      description: Status of the condition, one of True, False, Unknown
                      type: string
                    type:
                      description: Type of condition in CamelCase.
                      enum:
                      - Ready
                      type: string
                  required:
                  - status
                  - type
                  type: object
                type: array
            type: object
        required:
        - spec
        type: object
    served: true
    storage: true
    subresources:
      status: {}


================================================
FILE: deployment/manifests/deployment-lockbox.yaml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: lockbox-controller
  namespace: lockbox
spec:
  replicas: 1
  selector:
    matchLabels:
      app: lockbox
      component: controller
  template:
    metadata:
      labels:
        app: lockbox
        component: controller
    spec:
      serviceAccountName: lockbox-controller
      containers:
      - name: lockbox
        image: cloudflare/lockbox:v0.6.0
        ports:
        - containerPort: 8080
          name: http-metrics
        - containerPort: 8081
          name: http-api
        volumeMounts:
        - name: keypair
          mountPath: /etc/lockbox/
          readOnly: true
      volumes:
      - name: keypair
        secret:
          secretName: keypair
          defaultMode: 256


================================================
FILE: deployment/manifests/namespace-lockbox.yaml
================================================
apiVersion: v1
kind: Namespace
metadata:
  name: lockbox


================================================
FILE: deployment/manifests/service-lockbox.yaml
================================================
kind: Service
apiVersion: v1
metadata:
  name: lockbox
  namespace: lockbox
spec:
  ports:
  - port: 80
    targetPort: 8081
  selector:
    app: lockbox
    component: controller


================================================
FILE: deployment/manifests/serviceaccount-lockbox.yaml
================================================
apiVersion: v1
kind: ServiceAccount
metadata:
  name: lockbox-controller
  namespace: lockbox


================================================
FILE: deployment/rbac/proxier.yaml
================================================
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: lockbox-proxier
  namespace: lockbox
rules:
  - apiGroups:
      - ""
    resources:
      - "services/proxy"
    resourceNames:
      - "http:lockbox:"
    verbs:
      - "get"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: lockbox-proxier
  namespace: lockbox
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: lockbox-proxier
subjects:
  - kind: Group
    name: system:authenticated


================================================
FILE: deployment/rbac/role-binding.yaml
================================================
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: lockbox-controller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: lockbox-controller
subjects:
  - kind: ServiceAccount
    name: lockbox-controller
    namespace: lockbox


================================================
FILE: deployment/rbac/role.yaml
================================================
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: lockbox-controller
rules:
- apiGroups:
  - ""
  resources:
  - events
  verbs:
  - create
  - patch
- apiGroups:
  - ""
  resources:
  - secrets
  verbs:
  - create
  - get
  - list
  - patch
  - update
  - watch
- apiGroups:
  - lockbox.k8s.cloudflare.com
  resources:
  - lockboxes
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - lockbox.k8s.cloudflare.com
  resources:
  - lockboxes/status
  verbs:
  - get
  - patch
  - update


================================================
FILE: go.mod
================================================
module github.com/cloudflare/lockbox

go 1.21

toolchain go1.21.5

require (
	github.com/go-logr/zerologr v1.2.3
	github.com/google/go-cmp v0.6.0
	github.com/kevinburke/nacl v0.0.0-20210405173606-cd9060f5f776
	github.com/prometheus/client_golang v1.18.0
	github.com/prometheus/common v0.45.0
	github.com/rs/zerolog v1.29.1
	gotest.tools/v3 v3.4.0
	k8s.io/api v0.29.0
	k8s.io/apimachinery v0.29.0
	k8s.io/client-go v0.29.0
	k8s.io/utils v0.0.0-20230726121419-3b25d923346b
	sigs.k8s.io/controller-runtime v0.17.0
	sigs.k8s.io/controller-tools v0.13.0
	sigs.k8s.io/yaml v1.4.0
)

require (
	github.com/beorn7/perks v1.0.1 // indirect
	github.com/cespare/xxhash/v2 v2.2.0 // indirect
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/emicklei/go-restful/v3 v3.11.0 // indirect
	github.com/evanphx/json-patch v5.6.0+incompatible // indirect
	github.com/evanphx/json-patch/v5 v5.8.0 // indirect
	github.com/fatih/color v1.15.0 // indirect
	github.com/fsnotify/fsnotify v1.7.0 // indirect
	github.com/go-logr/logr v1.4.1 // indirect
	github.com/go-openapi/jsonpointer v0.19.6 // indirect
	github.com/go-openapi/jsonreference v0.20.2 // indirect
	github.com/go-openapi/swag v0.22.3 // indirect
	github.com/gobuffalo/flect v1.0.2 // indirect
	github.com/gogo/protobuf v1.3.2 // indirect
	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
	github.com/golang/protobuf v1.5.3 // indirect
	github.com/google/gnostic-models v0.6.8 // indirect
	github.com/google/gofuzz v1.2.0 // indirect
	github.com/google/uuid v1.3.0 // indirect
	github.com/imdario/mergo v0.3.12 // indirect
	github.com/inconshreveable/mousetrap v1.1.0 // indirect
	github.com/josharian/intern v1.0.0 // indirect
	github.com/json-iterator/go v1.1.12 // indirect
	github.com/mailru/easyjson v0.7.7 // indirect
	github.com/mattn/go-colorable v0.1.13 // indirect
	github.com/mattn/go-isatty v0.0.17 // indirect
	github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
	github.com/modern-go/reflect2 v1.0.2 // indirect
	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
	github.com/pkg/errors v0.9.1 // indirect
	github.com/prometheus/client_model v0.5.0 // indirect
	github.com/prometheus/procfs v0.12.0 // indirect
	github.com/spf13/cobra v1.7.0 // indirect
	github.com/spf13/pflag v1.0.5 // indirect
	golang.org/x/crypto v0.16.0 // indirect
	golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
	golang.org/x/mod v0.14.0 // indirect
	golang.org/x/net v0.19.0 // indirect
	golang.org/x/oauth2 v0.12.0 // indirect
	golang.org/x/sys v0.16.0 // indirect
	golang.org/x/term v0.15.0 // indirect
	golang.org/x/text v0.14.0 // indirect
	golang.org/x/time v0.3.0 // indirect
	golang.org/x/tools v0.16.1 // indirect
	gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
	google.golang.org/appengine v1.6.7 // indirect
	google.golang.org/protobuf v1.31.0 // indirect
	gopkg.in/inf.v0 v0.9.1 // indirect
	gopkg.in/yaml.v2 v2.4.0 // indirect
	gopkg.in/yaml.v3 v3.0.1 // indirect
	k8s.io/apiextensions-apiserver v0.29.0 // indirect
	k8s.io/component-base v0.29.0 // indirect
	k8s.io/klog/v2 v2.110.1 // indirect
	k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
	sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
	sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
)


================================================
FILE: go.sum
================================================
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch/v5 v5.8.0 h1:lRj6N9Nci7MvzrXuX6HFzU8XjmhPiXPlsKEy1u0KQro=
github.com/evanphx/json-patch/v5 v5.8.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
github.com/go-logr/zerologr v1.2.3 h1:up5N9vcH9Xck3jJkXzgyOxozT14R47IyDODz8LM1KSs=
github.com/go-logr/zerologr v1.2.3/go.mod h1:BxwGo7y5zgSHYR1BjbnHPyF/5ZjVKfKxAZANVu6E8Ho=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA=
github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kevinburke/nacl v0.0.0-20210405173606-cd9060f5f776 h1:W8T7zJRO9imecUZySwPkuXHosjp2MloqAY1eSAEEOIo=
github.com/kevinburke/nacl v0.0.0-20210405173606-cd9060f5f776/go.mod h1:VUp2yfq+wAk8hMl3NNN34fXjzUD9xMpGvUL8eSJz9Ns=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY=
github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw=
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
k8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A=
k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA=
k8s.io/apiextensions-apiserver v0.29.0 h1:0VuspFG7Hj+SxyF/Z/2T0uFbI5gb5LRgEyUVE3Q4lV0=
k8s.io/apiextensions-apiserver v0.29.0/go.mod h1:TKmpy3bTS0mr9pylH0nOt/QzQRrW7/h7yLdRForMZwc=
k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o=
k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis=
k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8=
k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38=
k8s.io/component-base v0.29.0 h1:T7rjd5wvLnPBV1vC4zWd/iWRbV8Mdxs+nGaoaFzGw3s=
k8s.io/component-base v0.29.0/go.mod h1:sADonFTQ9Zc9yFLghpDpmNXEdHyQmFIGbiuZbqAXQ1M=
k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0=
k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.17.0 h1:fjJQf8Ukya+VjogLO6/bNX9HE6Y2xpsO5+fyS26ur/s=
sigs.k8s.io/controller-runtime v0.17.0/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s=
sigs.k8s.io/controller-tools v0.13.0 h1:NfrvuZ4bxyolhDBt/rCZhDnx3M2hzlhgo5n3Iv2RykI=
sigs.k8s.io/controller-tools v0.13.0/go.mod h1:5vw3En2NazbejQGCeWKRrE7q4P+CW8/klfVqP8QZkgA=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=


================================================
FILE: pkg/apis/lockbox.k8s.cloudflare.com/v1/groupversion_info.go
================================================
// +kubebuilder:object:generate=true
// +groupName=lockbox.k8s.cloudflare.com

// Package v1 is the v1 version of the Lockbox API
package v1

import (
	"k8s.io/apimachinery/pkg/runtime/schema"
	"sigs.k8s.io/controller-runtime/pkg/scheme"
)

//go:generate controller-gen object crd paths=./. output:crd:artifacts:config=../../../../deployment/crds

var (
	// GroupVersion is group version used to register these objects
	GroupVersion = schema.GroupVersion{Group: "lockbox.k8s.cloudflare.com", Version: "v1"}

	// SchemeBuilder is used to add go types to the GroupVersionKind scheme
	SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}

	// AddToScheme adds the types in this group-version to the given scheme.
	AddToScheme = SchemeBuilder.AddToScheme
)

func init() {
	SchemeBuilder.Register(&Lockbox{}, &LockboxList{})
}


================================================
FILE: pkg/apis/lockbox.k8s.cloudflare.com/v1/lockbox.go
================================================
package v1

import (
	"github.com/kevinburke/nacl"
	"github.com/kevinburke/nacl/box"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const keySize = nacl.KeySize

// NewFromSecret creates a Lockbox wrapping the provided Secret. The value of each secret
// are individually encrypted using the provided key pair.
func NewFromSecret(secret corev1.Secret, namespace string, peer, pub, pri nacl.Key) *Lockbox {
	encNS := box.EasySeal([]byte(namespace), peer, pri)

	b := &Lockbox{
		ObjectMeta: metav1.ObjectMeta{
			Name:      secret.Name,
			Namespace: namespace,
		},
		Spec: LockboxSpec{
			Sender:    pub[:],
			Peer:      peer[:],
			Namespace: encNS,
			Data:      map[string][]byte{},
			Template: LockboxSecretTemplate{
				LockboxSecretTemplateMetadata: LockboxSecretTemplateMetadata{
					Labels:      secret.ObjectMeta.Labels,
					Annotations: secret.ObjectMeta.Annotations,
				},
				Type: secret.Type,
			},
		},
	}

	for key, value := range secret.Data {
		enc := box.EasySeal(value, peer, pri)
		b.Spec.Data[key] = enc
	}

	for key, value := range secret.StringData {
		enc := box.EasySeal([]byte(value), peer, pri)
		b.Spec.Data[key] = enc
	}

	return b
}

// UnlockInto decrypts each secret value into the provided secret.
func (in *Lockbox) UnlockInto(secret *corev1.Secret, pri nacl.Key) error {
	sender := new([keySize]byte)
	copy(sender[:], in.Spec.Sender)

	data := make(map[string][]byte, len(in.Spec.Data))
	for key, val := range in.Spec.Data {
		d, err := box.EasyOpen(val, sender, pri)
		if err != nil {
			return decryptSecretKeyError{error: err, key: key}
		}

		data[key] = d
	}

	secret.Data = data
	secret.Type = in.Spec.Template.Type
	secret.Labels = in.Spec.Template.Labels
	secret.Annotations = in.Spec.Template.Annotations

	return nil
}

// decryptSecretKeyError wraps error while decrypting data from a secret.
// This allows preserving the key for farther error messages.
type decryptSecretKeyError struct {
	error
	key string
}

// SecretKey returns the secret data key that triggered this error.
func (e decryptSecretKeyError) SecretKey() string {
	return e.key
}

// Unwrap implements Wrapper, returning the underlying error message.
func (e decryptSecretKeyError) Unwrap() error {
	return e.error
}

func (in *Lockbox) GetConditions() []Condition {
	return in.Status.Conditions
}

func (in *Lockbox) SetConditions(conditions []Condition) {
	in.Status.Conditions = conditions
}


================================================
FILE: pkg/apis/lockbox.k8s.cloudflare.com/v1/lockbox_test.go
================================================
package v1_test

import (
	"crypto/rand"
	"testing"

	v1 "github.com/cloudflare/lockbox/pkg/apis/lockbox.k8s.cloudflare.com/v1"
	"github.com/kevinburke/nacl"
	"github.com/kevinburke/nacl/box"
	"gotest.tools/v3/assert"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestUnlock(t *testing.T) {
	_, priKey, err := loadKeypair(t, "6a42b9fc2b011fb88c01741483e3bffe455bdab1ae35d0bb53a3c00d406d8836", "252173f975f0a0ddb198a7e5958c074203a0e9f44275e0b840f95d456c4acc2e")
	assert.NilError(t, err)

	lb := &v1.Lockbox{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "example",
			Namespace: "example",
			Labels: map[string]string{
				"type": "lockbox",
			},
			Annotations: map[string]string{
				"helm.sh/hook": "pre-install",
			},
		},
		Spec: v1.LockboxSpec{
			Sender:    []byte{0xb2, 0xa3, 0xf, 0x85, 0xa, 0x58, 0xcf, 0x94, 0x4c, 0x62, 0x37, 0xd4, 0xef, 0xf5, 0xed, 0x11, 0x52, 0xfa, 0x1b, 0xc3, 0xb0, 0x4d, 0x27, 0xd5, 0x58, 0x67, 0x61, 0x67, 0xe0, 0x10, 0xb1, 0x5c},
			Peer:      []byte{0x6a, 0x42, 0xb9, 0xfc, 0x2b, 0x1, 0x1f, 0xb8, 0x8c, 0x1, 0x74, 0x14, 0x83, 0xe3, 0xbf, 0xfe, 0x45, 0x5b, 0xda, 0xb1, 0xae, 0x35, 0xd0, 0xbb, 0x53, 0xa3, 0xc0, 0xd, 0x40, 0x6d, 0x88, 0x36},
			Namespace: []byte{0x4d, 0xa0, 0x73, 0x8b, 0x95, 0xc3, 0xd4, 0x64, 0xe9, 0xab, 0xd, 0xb7, 0x1e, 0x5, 0x10, 0xed, 0x4c, 0x2f, 0x8a, 0x66, 0x6d, 0xec, 0x7c, 0x5d, 0x9b, 0xa7, 0xb7, 0x88, 0x49, 0x8a, 0xb9, 0x7f, 0xf0, 0x30, 0xe0, 0xad, 0x49, 0x7c, 0x3f, 0xe3, 0x1c, 0x2e, 0xe9, 0xb1, 0x2a, 0x70, 0x28},
			Template: v1.LockboxSecretTemplate{
				LockboxSecretTemplateMetadata: v1.LockboxSecretTemplateMetadata{
					Labels: map[string]string{
						"type": "secret",
					},
					Annotations: map[string]string{
						"wave": "ignore",
					},
				},
			},
			Data: map[string][]byte{
				"test":  {0x7b, 0xca, 0x32, 0x90, 0xf7, 0x97, 0x3b, 0x6, 0xfb, 0x7c, 0xdc, 0x3a, 0x25, 0x82, 0x29, 0xdf, 0x9d, 0x1e, 0x46, 0x8d, 0xd4, 0x99, 0x49, 0x2, 0x63, 0x56, 0x54, 0x64, 0xae, 0x9e, 0xf2, 0xc0, 0x35, 0xf5, 0xf1, 0xcb, 0x67, 0xb7, 0xe2, 0xb1, 0x14, 0x42, 0x71, 0xc},
				"test1": {0x2c, 0x68, 0xed, 0x53, 0x55, 0x55, 0xe2, 0x2d, 0x71, 0x96, 0x85, 0xfd, 0xdb, 0x93, 0x1e, 0x77, 0x91, 0x2d, 0x76, 0xba, 0xae, 0x46, 0x30, 0x9e, 0xb6, 0x65, 0xa2, 0x49, 0xfe, 0x78, 0xc0, 0xcb, 0x6d, 0xf, 0xa8, 0xeb, 0xa8, 0xfc, 0xc0, 0xa0, 0xdc, 0x4, 0x16, 0x7, 0xa0},
			},
		},
	}

	secret := &corev1.Secret{}
	expected := &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Labels: map[string]string{
				"type": "secret",
			},
			Annotations: map[string]string{
				"wave": "ignore",
			},
		},
		Data: map[string][]byte{
			"test":  {0x74, 0x65, 0x73, 0x74},
			"test1": {0x74, 0x65, 0x73, 0x74, 0x31},
		},
	}

	assert.NilError(t, lb.UnlockInto(secret, priKey))
	assert.DeepEqual(t, secret, expected)
}

func TestUnlockErr(t *testing.T) {
	_, priKey, err := loadKeypair(t, "6a42b9fc2b011fb88c01741483e3bffe455bdab1ae35d0bb53a3c00d406d8836", "252173f975f0a0ddb198a7e5958c074203a0e9f44275e0b840f95d456c4acc2e")
	assert.NilError(t, err)

	lb := &v1.Lockbox{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "example",
			Namespace: "example",
			Labels: map[string]string{
				"type": "lockbox",
			},
			Annotations: map[string]string{
				"helm.sh/hook": "pre-install",
			},
		},
		Spec: v1.LockboxSpec{
			Sender:    []byte{0x46, 0xa5, 0xfa, 0xa9, 0xe1, 0xe6, 0xd8, 0x48, 0x14, 0xfd, 0x52, 0x67, 0x98, 0x39, 0x12, 0xda, 0x78, 0x61, 0x95, 0x84, 0x8d, 0xbb, 0xd2, 0x1f, 0x36, 0xaa, 0xdc, 0x6a, 0x18, 0x5c, 0xe5, 0x77},
			Peer:      []byte{0x6a, 0x42, 0xb9, 0xfc, 0x2b, 0x01, 0x1f, 0xb8, 0x8c, 0x01, 0x74, 0x14, 0x83, 0xe3, 0xbf, 0xfe, 0x45, 0x5b, 0xda, 0xb1, 0xae, 0x35, 0xd0, 0xbb, 0x53, 0xa3, 0xc0, 0x0d, 0x40, 0x6d, 0x88, 0x36},
			Namespace: []byte{0x4d, 0xa0, 0x73, 0x8b, 0x95, 0xc3, 0xd4, 0x64, 0xe9, 0xab, 0xd, 0xb7, 0x1e, 0x5, 0x10, 0xed, 0x4c, 0x2f, 0x8a, 0x66, 0x6d, 0xec, 0x7c, 0x5d, 0x9b, 0xa7, 0xb7, 0x88, 0x49, 0x8a, 0xb9, 0x7f, 0xf0, 0x30, 0xe0, 0xad, 0x49, 0x7c, 0x3f, 0xe3, 0x1c, 0x2e, 0xe9, 0xb1, 0x2a, 0x70, 0x28},
			Template: v1.LockboxSecretTemplate{
				LockboxSecretTemplateMetadata: v1.LockboxSecretTemplateMetadata{
					Labels: map[string]string{
						"type": "secret",
					},
					Annotations: map[string]string{
						"wave": "ignore",
					},
				},
			},
			Data: map[string][]byte{
				"test":  {0x7b, 0xca, 0x32, 0x90, 0xf7, 0x97, 0x3b, 0x6, 0xfb, 0x7c, 0xdc, 0x3a, 0x25, 0x82, 0x29, 0xdf, 0x9d, 0x1e, 0x46, 0x8d, 0xd4, 0x99, 0x49, 0x2, 0x63, 0x56, 0x54, 0x64, 0xae, 0x9e, 0xf2, 0xc0, 0x35, 0xf5, 0xf1, 0xcb, 0x67, 0xb7, 0xe2, 0xb1, 0x14, 0x42, 0x71, 0xc},
				"test1": {0x2c, 0x68, 0xed, 0x53, 0x55, 0x55, 0xe2, 0x2d, 0x71, 0x96, 0x85, 0xfd, 0xdb, 0x93, 0x1e, 0x77, 0x91, 0x2d, 0x76, 0xba, 0xae, 0x46, 0x30, 0x9e, 0xb6, 0x65, 0xa2, 0x49, 0xfe, 0x78, 0xc0, 0xcb, 0x6d, 0xf, 0xa8, 0xeb, 0xa8, 0xfc, 0xc0, 0xa0, 0xdc, 0x4, 0x16, 0x7, 0xa0},
			},
		},
	}

	secret := &corev1.Secret{}
	expected := &corev1.Secret{}

	err = lb.UnlockInto(secret, priKey)
	assert.ErrorContains(t, err, "Could not decrypt invalid input")
	assert.DeepEqual(t, secret, expected)
}

func TestLockUnlock(t *testing.T) {
	senderPubKey, senderPriKey, _ := box.GenerateKey(rand.Reader)
	serverPubKey, serverPriKey, _ := box.GenerateKey(rand.Reader)

	secret := corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Labels: map[string]string{
				"type": "secret",
			},
		},
		Data: map[string][]byte{
			"test": {0x74, 0x65, 0x73, 0x74},
		},
	}

	lb := v1.NewFromSecret(secret, "namespace", serverPubKey, senderPubKey, senderPriKey)
	unlockedSecret := &corev1.Secret{}
	expectedSecret := &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Labels: map[string]string{
				"type": "secret",
			},
		},
		Data: map[string][]byte{
			"test": {0x74, 0x65, 0x73, 0x74},
		},
	}

	assert.NilError(t, lb.UnlockInto(unlockedSecret, serverPriKey))
	assert.DeepEqual(t, unlockedSecret, expectedSecret)
}

func loadKeypair(t *testing.T, pub, pri string) (pubKey, priKey nacl.Key, err error) {
	t.Helper()

	pubKey, err = nacl.Load(pub)
	if err != nil {
		return
	}

	priKey, err = nacl.Load(pri)
	return
}


================================================
FILE: pkg/apis/lockbox.k8s.cloudflare.com/v1/types.go
================================================
package v1

import (
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="SecretType",type=string,JSONPath=`.spec.template.type`
// +kubebuilder:printcolumn:name="Peer",type=string,JSONPath=`.spec.peer`

// Lockbox is a struct wrapping the LockboxSpec in standard API server
// metadata fields.
type Lockbox struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	// Desired state of the Lockbox resource.
	Spec LockboxSpec `json:"spec"`

	// Status of the Lockbox. This is set and managed automatically.
	// +optional
	Status LockboxStatus `json:"status,omitempty"`
}

// LockboxSpec is a struct wrapping the encrypted secrets along with the
// public keys of the sender and server.
type LockboxSpec struct {
	// Sender stores the public key used to lock this Lockbox.
	Sender []byte `json:"sender"`

	// Peer stores the public key that can unlock this Lockbox.
	Peer []byte `json:"peer"`

	// Namespace stores an encrypted copy of which namespace this Lockbox is locked
	// for, ensuring it cannot be deployed to another namespace under an attacker's
	// control.
	Namespace []byte `json:"namespace"`

	// Data contains the secret data, encrypted to the Peer's public key. Each key in the
	// data map must consist of alphanumeric characters, '-', '_', or '.'.
	Data map[string][]byte `json:"data"`

	// Template defines the structure of the Secret that will be
	// created from this Lockbox.
	// +optional
	Template LockboxSecretTemplate `json:"template,omitempty"`
}

// LockboxSecretTemplate defines structure of API metadata fields
// of Secrets controlled by a Lockbox.
type LockboxSecretTemplate struct {
	LockboxSecretTemplateMetadata `json:"metadata,omitempty"`

	// Type is used to facilitate programmatic handling of secret data.
	Type corev1.SecretType `json:"type,omitempty"`
}

type LockboxSecretTemplateMetadata struct {
	// Map of string keys and values that can be used to organize and categorize
	// (scope and select) objects. May match selectors of replication
	// controllers and services. More info:
	// http://kubernetes.io/docs/user-guide/labels
	// +optional
	Labels map[string]string `json:"labels,omitempty"`

	// Annotations is an unstructured key value map stored with a resource that
	// may be set by external tools to store and retrieve arbitrary metadata.
	// They are not queryable and should be preserved when modifying objects.
	// More info: http://kubernetes.io/docs/user-guide/annotations
	// +optional
	Annotations map[string]string `json:"annotations,omitempty"`
}

// LockboxStatus contains status information about a Lockbox.
type LockboxStatus struct {
	// List of status conditions to indicate the status of a Lockbox.
	// +optional
	Conditions []Condition `json:"conditions,omitempty"`
}

// Condition contains condition information for a Lockbox.
type Condition struct {
	// Type of condition in CamelCase.
	// +required
	Type ConditionType `json:"type"`

	// Status of the condition, one of True, False, Unknown
	// +required
	Status corev1.ConditionStatus `json:"status"`

	// Severity provides explicit classification of Reason code, so that users or machines
	// can immediately understand the current situation and act accordingly.
	// The Severity field MUST be set only when Status=False.
	// +optional
	Severity ConditionSeverity `json:"severity"`

	// LastTransitionTime marks when the condition last transitioned from one status to another.
	// This should be when the underlying condition changed. If that is not known, then using the time
	// when the API field changed is acceptable.
	// +required
	LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`

	// The reason for the condition's last transition in CamelCase.
	// +optional
	Reason string `json:"reason,omitempty"`

	// A message is the human readable message indicating details about the transition.
	// The field may be empty.
	// +optional
	Message string `json:"message,omitempty"`
}

// +kubebuilder:validation:Enum=Ready
type ConditionType string

const (
	ReadyCondition ConditionType = "Ready"
)

// +kubebuilder:validation:Enum=Error;Warning;Info
type ConditionSeverity string

const (
	ConditionSeverityError   ConditionSeverity = "Error"
	ConditionSeverityWarning ConditionSeverity = "Warning"
	ConditionSeverityInfo    ConditionSeverity = "Info"
	ConditionSeverityNone    ConditionSeverity = ""
)

// +kubebuilder:object:root=true

// LockboxList is a Lockbox-specific version of metav1.List.
type LockboxList struct {
	metav1.TypeMeta
	metav1.ListMeta

	Items []Lockbox
}


================================================
FILE: pkg/apis/lockbox.k8s.cloudflare.com/v1/zz_generated.deepcopy.go
================================================
//go:build !ignore_autogenerated

// Code generated by controller-gen. DO NOT EDIT.

package v1

import (
	runtime "k8s.io/apimachinery/pkg/runtime"
)

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Condition) DeepCopyInto(out *Condition) {
	*out = *in
	in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition.
func (in *Condition) DeepCopy() *Condition {
	if in == nil {
		return nil
	}
	out := new(Condition)
	in.DeepCopyInto(out)
	return out
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Lockbox) DeepCopyInto(out *Lockbox) {
	*out = *in
	out.TypeMeta = in.TypeMeta
	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
	in.Spec.DeepCopyInto(&out.Spec)
	in.Status.DeepCopyInto(&out.Status)
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Lockbox.
func (in *Lockbox) DeepCopy() *Lockbox {
	if in == nil {
		return nil
	}
	out := new(Lockbox)
	in.DeepCopyInto(out)
	return out
}

// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Lockbox) DeepCopyObject() runtime.Object {
	if c := in.DeepCopy(); c != nil {
		return c
	}
	return nil
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LockboxList) DeepCopyInto(out *LockboxList) {
	*out = *in
	out.TypeMeta = in.TypeMeta
	in.ListMeta.DeepCopyInto(&out.ListMeta)
	if in.Items != nil {
		in, out := &in.Items, &out.Items
		*out = make([]Lockbox, len(*in))
		for i := range *in {
			(*in)[i].DeepCopyInto(&(*out)[i])
		}
	}
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LockboxList.
func (in *LockboxList) DeepCopy() *LockboxList {
	if in == nil {
		return nil
	}
	out := new(LockboxList)
	in.DeepCopyInto(out)
	return out
}

// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *LockboxList) DeepCopyObject() runtime.Object {
	if c := in.DeepCopy(); c != nil {
		return c
	}
	return nil
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LockboxSecretTemplate) DeepCopyInto(out *LockboxSecretTemplate) {
	*out = *in
	in.LockboxSecretTemplateMetadata.DeepCopyInto(&out.LockboxSecretTemplateMetadata)
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LockboxSecretTemplate.
func (in *LockboxSecretTemplate) DeepCopy() *LockboxSecretTemplate {
	if in == nil {
		return nil
	}
	out := new(LockboxSecretTemplate)
	in.DeepCopyInto(out)
	return out
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LockboxSecretTemplateMetadata) DeepCopyInto(out *LockboxSecretTemplateMetadata) {
	*out = *in
	if in.Labels != nil {
		in, out := &in.Labels, &out.Labels
		*out = make(map[string]string, len(*in))
		for key, val := range *in {
			(*out)[key] = val
		}
	}
	if in.Annotations != nil {
		in, out := &in.Annotations, &out.Annotations
		*out = make(map[string]string, len(*in))
		for key, val := range *in {
			(*out)[key] = val
		}
	}
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LockboxSecretTemplateMetadata.
func (in *LockboxSecretTemplateMetadata) DeepCopy() *LockboxSecretTemplateMetadata {
	if in == nil {
		return nil
	}
	out := new(LockboxSecretTemplateMetadata)
	in.DeepCopyInto(out)
	return out
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LockboxSpec) DeepCopyInto(out *LockboxSpec) {
	*out = *in
	if in.Sender != nil {
		in, out := &in.Sender, &out.Sender
		*out = make([]byte, len(*in))
		copy(*out, *in)
	}
	if in.Peer != nil {
		in, out := &in.Peer, &out.Peer
		*out = make([]byte, len(*in))
		copy(*out, *in)
	}
	if in.Namespace != nil {
		in, out := &in.Namespace, &out.Namespace
		*out = make([]byte, len(*in))
		copy(*out, *in)
	}
	if in.Data != nil {
		in, out := &in.Data, &out.Data
		*out = make(map[string][]byte, len(*in))
		for key, val := range *in {
			var outVal []byte
			if val == nil {
				(*out)[key] = nil
			} else {
				inVal := (*in)[key]
				in, out := &inVal, &outVal
				*out = make([]byte, len(*in))
				copy(*out, *in)
			}
			(*out)[key] = outVal
		}
	}
	in.Template.DeepCopyInto(&out.Template)
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LockboxSpec.
func (in *LockboxSpec) DeepCopy() *LockboxSpec {
	if in == nil {
		return nil
	}
	out := new(LockboxSpec)
	in.DeepCopyInto(out)
	return out
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LockboxStatus) DeepCopyInto(out *LockboxStatus) {
	*out = *in
	if in.Conditions != nil {
		in, out := &in.Conditions, &out.Conditions
		*out = make([]Condition, len(*in))
		for i := range *in {
			(*in)[i].DeepCopyInto(&(*out)[i])
		}
	}
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LockboxStatus.
func (in *LockboxStatus) DeepCopy() *LockboxStatus {
	if in == nil {
		return nil
	}
	out := new(LockboxStatus)
	in.DeepCopyInto(out)
	return out
}


================================================
FILE: pkg/flagvar/enum.go
================================================
package flagvar

import (
	"errors"
	"fmt"
	"strings"
)

var ErrInvalidEnum = errors.New("invalid enum option")

type Enum struct {
	Choices []string
	Value   string
}

func (e *Enum) Help() string {
	return fmt.Sprintf("one of %v", e.Choices)
}

func (e *Enum) Set(v string) error {
	for _, c := range e.Choices {
		if strings.EqualFold(c, v) {
			e.Value = strings.ToLower(v)
			return nil
		}
	}

	return ErrInvalidEnum
}

func (e *Enum) String() string {
	if e == nil {
		return ""
	}

	return e.Value
}


================================================
FILE: pkg/flagvar/enum_test.go
================================================
package flagvar_test

import (
	"testing"

	"github.com/cloudflare/lockbox/pkg/flagvar"
	"gotest.tools/v3/assert"
)

func TestEnumString(t *testing.T) {
	type testCase struct {
		name     string
		fv       *flagvar.Enum
		expected string
	}

	run := func(t *testing.T, tc testCase) {
		actual := tc.fv.String()
		assert.Equal(t, actual, tc.expected)
	}

	testCases := []testCase{
		{
			name:     "non-nil receiver",
			fv:       &flagvar.Enum{Value: "yaml"},
			expected: "yaml",
		},
		{
			name:     "nil receiver",
			fv:       nil,
			expected: "",
		},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			run(t, tc)
		})
	}
}

func TestEnumSet(t *testing.T) {
	type testCase struct {
		name     string
		input    string
		expected string
		err      error
	}

	run := func(t *testing.T, tc testCase) {
		fv := &flagvar.Enum{
			Choices: []string{"yaml", "json"},
		}
		err := fv.Set(tc.input)

		if err != nil {
			assert.ErrorIs(t, err, tc.err)
		} else {
			assert.Equal(t, fv.Value, tc.expected)
		}
	}

	testCases := []testCase{
		{
			name:     "valid enum option",
			input:    "yaml",
			expected: "yaml",
		},
		{
			name:     "ignores option capitalization",
			input:    "YaMl",
			expected: "yaml",
		},
		{
			name:  "invalid enum option",
			input: "cue",
			err:   flagvar.ErrInvalidEnum,
		},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			run(t, tc)
		})
	}
}


================================================
FILE: pkg/flagvar/file.go
================================================
package flagvar

import (
	"os"
)

// File is a flag.Value for file paths. Returns any errors from os.Stat.
type File struct {
	Value string
}

// Help returns a string to include in the flag's help message.
func (f *File) Help() string {
	return "file path"
}

// Set implements flag.Value by checking for the file's existence through
// using os.Stat. Any error returned by os.Stat is returned by this function.
func (f *File) Set(v string) error {
	_, err := os.Stat(v)
	f.Value = v

	return err
}

// String implements flag.Value by returning the current file path.
func (f *File) String() string {
	if f == nil {
		return ""
	}

	return f.Value
}

// Type implements pflag.Value by noting our Value is string typed.
func (f *File) Type() string {
	return "string"
}


================================================
FILE: pkg/flagvar/file_test.go
================================================
package flagvar_test

import (
	"io/fs"
	"path/filepath"
	"testing"

	"github.com/cloudflare/lockbox/pkg/flagvar"
	"gotest.tools/v3/assert"
)

func TestFileString(t *testing.T) {
	type testCase struct {
		name     string
		fv       *flagvar.File
		expected string
	}

	run := func(t *testing.T, tc testCase) {
		actual := tc.fv.String()
		assert.Equal(t, actual, tc.expected)
	}

	testCases := []testCase{
		{
			name:     "non-nil receiver",
			fv:       &flagvar.File{Value: "/path/to/default.log"},
			expected: "/path/to/default.log",
		},
		{
			name:     "nil receiver",
			fv:       nil,
			expected: "",
		},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			run(t, tc)
		})
	}
}

func TestFileSet(t *testing.T) {
	type testCase struct {
		name     string
		input    string
		expected string
		err      error
	}

	run := func(t *testing.T, tc testCase) {
		fv := &flagvar.File{}

		err := fv.Set(tc.input)
		if tc.err != nil {
			assert.ErrorIs(t, err, tc.err)
		} else {
			assert.Equal(t, fv.Value, tc.expected)
		}
	}

	testCases := []testCase{
		{
			name:     "file exists",
			input:    filepath.Join("testdata", "file"),
			expected: "testdata/file",
		},
		{
			name:  "file does not exist",
			input: filepath.Join("testdata", "file_nonexistant.go"),
			err:   fs.ErrNotExist,
		},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			run(t, tc)
		})
	}
}


================================================
FILE: pkg/flagvar/tcp_addr.go
================================================
package flagvar

import "net"

// TCPAddr is a flag.Value for file paths. Returns any errors from net.ResolveTCPAddr.
type TCPAddr struct {
	Network string
	Value   *net.TCPAddr
	Text    string
}

// Help returns a string to include in the flag's help message.
func (t *TCPAddr) Help() string {
	return "TCP address in host:port format"
}

// Set implements flag.Value by parsing the provided address using net.ResolveTCPAddr.
// Any error return is returned by this function.
func (t *TCPAddr) Set(v string) error {
	network := "tcp"
	if t.Network != "" {
		network = t.Network
	}

	tcpAddr, err := net.ResolveTCPAddr(network, v)
	t.Text = v
	t.Value = tcpAddr

	return err
}

// String implements flag.Value by returning the current Text.
func (t *TCPAddr) String() string {
	if t == nil {
		return ""
	}

	return t.Text
}

// Type implements pflag.Value by noting our Value is net.TCPAddr typed.
func (t *TCPAddr) Type() string {
	return "net.TCPAddr"
}


================================================
FILE: pkg/flagvar/tcp_addr_test.go
================================================
package flagvar_test

import (
	"net"
	"testing"

	"github.com/cloudflare/lockbox/pkg/flagvar"
	"gotest.tools/v3/assert"
)

func TestTCPAddrString(t *testing.T) {
	type testCase struct {
		name     string
		fv       *flagvar.TCPAddr
		expected string
	}

	run := func(t *testing.T, tc testCase) {
		actual := tc.fv.String()
		assert.Equal(t, actual, tc.expected)
	}

	testCases := []testCase{
		{
			name:     "non-nil receiver",
			fv:       &flagvar.TCPAddr{Text: ":8080"},
			expected: ":8080",
		},
		{
			name:     "nil receiver",
			fv:       nil,
			expected: "",
		},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			run(t, tc)
		})
	}
}

func TestTCPAddrSet(t *testing.T) {
	type testCase struct {
		name     string
		input    string
		expected *net.TCPAddr
		err      string
	}

	run := func(t *testing.T, tc testCase) {
		fv := flagvar.TCPAddr{}
		err := fv.Set(tc.input)

		if err != nil {
			assert.Error(t, err, tc.err)
		} else {
			assert.DeepEqual(t, fv.Value, tc.expected)
		}
	}

	testCases := []testCase{
		{
			name:  "host:port address",
			input: "127.0.0.1:8080",
			expected: &net.TCPAddr{
				IP:   net.ParseIP("127.0.0.1"),
				Port: 8080,
			},
		},
		{
			name:  "port-only address",
			input: ":8080",
			expected: &net.TCPAddr{
				Port: 8080,
			},
		},
		{
			name:  "IPv6 support",
			input: "[::1]:8080",
			expected: &net.TCPAddr{
				IP:   net.ParseIP("::1"),
				Port: 8080,
			},
		},
		{
			name:     "invalid address",
			input:    "google.com",
			expected: nil,
			err:      "address google.com: missing port in address",
		},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			run(t, tc)
		})
	}
}


================================================
FILE: pkg/flagvar/testdata/file
================================================


================================================
FILE: pkg/lockbox-controller/secretreconciler.go
================================================
package controller

import (
	"context"
	"encoding/base64"
	"fmt"

	lockboxv1 "github.com/cloudflare/lockbox/pkg/apis/lockbox.k8s.cloudflare.com/v1"
	"github.com/cloudflare/lockbox/pkg/util/conditions"
	"github.com/kevinburke/nacl"
	"github.com/kevinburke/nacl/box"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/tools/record"
	"sigs.k8s.io/controller-runtime/pkg/client"
	clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

//go:generate controller-gen rbac:roleName=lockbox-controller paths=./. output:rbac:artifacts:config=../../deployment/rbac

// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;patch;update
// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch
// +kubebuilder:rbac:groups="lockbox.k8s.cloudflare.com",resources=lockboxes,verbs=get;list;watch
// +kubebuilder:rbac:groups="lockbox.k8s.cloudflare.com",resources=lockboxes/status,verbs=get;update;patch

const keySize = nacl.KeySize

// SecretReconcilerOption allows for functional options to modify the SecretReconciler
type SecretReconcilerOption func(s *SecretReconciler)

// SecretReconciler implements the reconciliation logic for Lockbox secrets.
type SecretReconciler struct {
	pubKey, priKey nacl.Key

	client   client.Client
	recorder record.EventRecorder
}

// NewSecretReconciler creates a reconciler controller for the provided keypair and options.
//
// If not mutated by any options, the reconciler uses a noop API client and events recorder.
func NewSecretReconciler(pubKey, priKey nacl.Key, options ...SecretReconcilerOption) *SecretReconciler {
	sr := &SecretReconciler{
		pubKey:   pubKey,
		priKey:   priKey,
		client:   clientfake.NewClientBuilder().Build(),
		recorder: &record.FakeRecorder{},
	}

	for _, opt := range options {
		opt(sr)
	}

	return sr
}

// Reconcile implements reconcile.Reconciler by ensuring Lockbox controlled Secrets are as described.
func (s *SecretReconciler) Reconcile(ctx context.Context, lb *lockboxv1.Lockbox) (reconcile.Result, error) {
	if len(lb.Spec.Sender) != keySize {
		msg := fmt.Sprintf("invalid sender key length, got %d wanted %d", len(lb.Spec.Sender), keySize)

		s.recorder.Eventf(lb, "Warning", "InvalidKeyLength", msg)
		conditions.Set(lb, conditions.FalseCondition(lockboxv1.ReadyCondition, "InvalidKeyLength", lockboxv1.ConditionSeverityError, msg))
		_ = s.client.Status().Update(ctx, lb)
		return reconcile.Result{}, fmt.Errorf("incorrect sender key length: %d, should be %d", len(lb.Spec.Sender), keySize)
	}
	if len(lb.Spec.Peer) != keySize {
		msg := fmt.Sprintf("invalid peer key length, got %d wanted %d", len(lb.Spec.Peer), keySize)

		s.recorder.Eventf(lb, "Warning", "InvalidKeyLength", msg)
		conditions.Set(lb, conditions.FalseCondition(lockboxv1.ReadyCondition, "InvalidKeyLength", lockboxv1.ConditionSeverityError, msg))
		_ = s.client.Status().Update(ctx, lb)
		return reconcile.Result{}, fmt.Errorf("incorrect peer key length: %d, should be %d", len(lb.Spec.Peer), keySize)
	}

	peerKey := new([keySize]byte)
	copy(peerKey[:], lb.Spec.Peer)

	if !nacl.Verify32(peerKey, s.pubKey) {
		msg := fmt.Sprintf("lockbox has unknown peer key %q", base64.StdEncoding.EncodeToString(lb.Spec.Peer))

		s.recorder.Eventf(lb, "Warning", "UnknownPeerKey", msg)
		conditions.Set(lb, conditions.FalseCondition(lockboxv1.ReadyCondition, "UnknownPeerKey", lockboxv1.ConditionSeverityError, msg))
		_ = s.client.Status().Update(ctx, lb)
		return reconcile.Result{}, fmt.Errorf("unknown peer key")
	}

	sender := new([keySize]byte)
	copy(sender[:], lb.Spec.Sender)

	secret := &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      lb.Name,
			Namespace: lb.Namespace,
		},
	}

	namespace, err := box.EasyOpen(lb.Spec.Namespace, sender, s.priKey)
	if err != nil {
		msg := fmt.Sprintf("unable to open lockbox with peer key %q", base64.StdEncoding.EncodeToString(lb.Spec.Peer))

		s.recorder.Eventf(lb, "Warning", "InvalidLockbox", msg)
		conditions.Set(lb, conditions.FalseCondition(lockboxv1.ReadyCondition, "InvalidLockbox", lockboxv1.ConditionSeverityError, msg))
		_ = s.client.Status().Update(ctx, lb)
		return reconcile.Result{}, err
	}

	if string(namespace) != lb.Namespace {
		msg := fmt.Sprintf("locked for namespace %q, found in namespace %s", namespace, lb.Namespace)

		s.recorder.Eventf(lb, "Warning", "InvalidNamespace", msg)
		conditions.Set(lb, conditions.FalseCondition(lockboxv1.ReadyCondition, "InvalidNamespace", lockboxv1.ConditionSeverityWarning, msg))
		_ = s.client.Status().Update(ctx, lb)
		return reconcile.Result{}, fmt.Errorf("incorrect namespace: %s, should be %s", namespace, lb.Namespace)
	}

	_, err = controllerutil.CreateOrPatch(
		ctx,
		s.client,
		secret,
		s.reconcileExisting(lb, sender, secret))

	if err != nil {
		conditions.Set(lb, conditions.FalseCondition(lockboxv1.ReadyCondition, "InvalidLockbox", lockboxv1.ConditionSeverityWarning, err.Error()))
		_ = s.client.Status().Update(ctx, lb)
		return reconcile.Result{}, err
	}

	conditions.Set(lb, conditions.TrueCondition(lockboxv1.ReadyCondition))
	_ = s.client.Status().Update(ctx, lb)
	return reconcile.Result{}, nil
}

// reconcileExisting returns a function suitable for controllerutil.CreateOrUpdate that mutates a Secret object
// to reflect the desired state.
func (s *SecretReconciler) reconcileExisting(lb *lockboxv1.Lockbox, sender nacl.Key, secret *corev1.Secret) func() error {
	return func() error {
		if err := controllerutil.SetControllerReference(lb, secret, s.client.Scheme()); err != nil {
			switch err := err.(type) {
			case decryptSecretKeyErrorer:
				s.recorder.Eventf(lb, "Warning", "InvalidLockbox", "lockbox contained key %q that could not be unlocked", err.SecretKey())
			default:
				s.recorder.Eventf(lb, "Warning", "InvalidLockbox", "lockbox could not be unlocked")
			}

			return err
		}

		return lb.UnlockInto(secret, s.priKey)
	}
}

// WithRecorder sets the EventRecorder used by the SecretReconciler.
func WithRecorder(r record.EventRecorder) SecretReconcilerOption {
	return func(s *SecretReconciler) {
		s.recorder = r
	}
}

// WithClient sets the API Client used by the SecretReconciler
func WithClient(c client.Client) SecretReconcilerOption {
	return func(s *SecretReconciler) {
		s.client = c
	}
}

// decryptSecretKeyErrorer matches the unexported error type, to
// fetch the secret data key that triggered the error.
type decryptSecretKeyErrorer interface {
	SecretKey() string
}


================================================
FILE: pkg/lockbox-controller/secretreconciler_suite_test.go
================================================
//go:build suite
// +build suite

package controller_test

import (
	"context"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"testing"

	lockboxv1 "github.com/cloudflare/lockbox/pkg/apis/lockbox.k8s.cloudflare.com/v1"
	. "github.com/cloudflare/lockbox/pkg/lockbox-controller"
	"github.com/go-logr/zerologr"
	"github.com/google/go-cmp/cmp/cmpopts"
	"github.com/rs/zerolog"
	"gotest.tools/v3/assert"
	"gotest.tools/v3/poll"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/rest"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/builder"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/envtest"
	logf "sigs.k8s.io/controller-runtime/pkg/log"
	"sigs.k8s.io/controller-runtime/pkg/manager"
	metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

var cfg *rest.Config

func TestMain(m *testing.M) {
	zl := zerolog.New(os.Stderr)
	logf.SetLogger(zerologr.New(&zl))
	t := &envtest.Environment{
		CRDDirectoryPaths: []string{filepath.Join("..", "..", "deployment", "crds")},
	}
	lockboxv1.AddToScheme(scheme.Scheme)

	var err error
	if cfg, err = t.Start(); err != nil {
		log.Fatal(err)
	}

	code := m.Run()
	t.Stop()
	os.Exit(code)
}

func TestSuiteSecretReconciler(t *testing.T) {
	type testCase struct {
		name        string
		lockboxName string
		resources   []client.Object
		expected    *corev1.Secret
	}

	pubKey, priKey, err := loadKeypair(t, "6a42b9fc2b011fb88c01741483e3bffe455bdab1ae35d0bb53a3c00d406d8836", "252173f975f0a0ddb198a7e5958c074203a0e9f44275e0b840f95d456c4acc2e")
	assert.NilError(t, err)

	setup := func(t *testing.T, tc testCase) {
		mgr, err := manager.New(cfg, manager.Options{
			Metrics: metricsserver.Options{
				BindAddress: "0",
			},
			Scheme: scheme.Scheme,
		})
		assert.NilError(t, err)

		sr := NewSecretReconciler(pubKey, priKey, WithClient(mgr.GetClient()))
		err = builder.
			ControllerManagedBy(mgr).
			For(&lockboxv1.Lockbox{}).
			Owns(&corev1.Secret{}).
			Complete(reconcile.AsReconciler(mgr.GetClient(), sr))
		assert.NilError(t, err)

		ctx, cancel := context.WithCancel(context.Background())
		go func() {
			mgr.Start(ctx)
		}()
		t.Cleanup(cancel)
	}

	run := func(t *testing.T, tc testCase) {
		c, err := client.New(cfg, client.Options{})
		assert.NilError(t, err)

		for _, r := range tc.resources {
			c.Create(context.Background(), r)
		}

		secret := &corev1.Secret{}
		poll.WaitOn(t, func(t poll.LogT) poll.Result {
			err := c.Get(context.Background(), client.ObjectKey{
				Name:      "example",
				Namespace: "default",
			}, secret)

			if err == nil {
				return poll.Success()
			}

			if apierrors.IsNotFound(err) {
				return poll.Continue("secret was not found")
			}

			return poll.Error(err)
		})

		cm := &corev1.ConfigMap{}
		c.Get(context.Background(), client.ObjectKey{
			Name:      "example",
			Namespace: "default",
		}, cm)

		fmt.Printf("cm: %+v\n", *cm)

		assert.DeepEqual(t, secret, tc.expected,
			cmpopts.IgnoreFields(metav1.ObjectMeta{}, "UID", "ResourceVersion", "CreationTimestamp", "ManagedFields"),
			cmpopts.IgnoreFields(metav1.OwnerReference{}, "UID"),
		)

		for _, r := range tc.resources {
			c.Delete(context.Background(), r)
		}
		// delete the created resource too, as there's no garbage collector
		c.Delete(context.Background(), tc.expected)
	}

	testCases := []testCase{
		{
			name:        "create secret",
			lockboxName: "example",
			resources: []client.Object{
				&lockboxv1.Lockbox{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "example",
						Namespace: "default",
						Labels: map[string]string{
							"type": "lockbox",
						},
						Annotations: map[string]string{
							"helm.sh/hook": "pre-install",
						},
					},
					Spec: lockboxv1.LockboxSpec{
						Sender:    []byte{0x74, 0xbd, 0xd8, 0x82, 0xf7, 0xd5, 0x87, 0xde, 0x08, 0x79, 0xf0, 0x9b, 0x35, 0x15, 0xf5, 0x2d, 0x1f, 0xb0, 0x26, 0xb3, 0x20, 0xe1, 0xe1, 0xd8, 0x5c, 0x5a, 0x0e, 0x1d, 0xfb, 0x80, 0x87, 0x23},
						Peer:      []byte{0x6a, 0x42, 0xb9, 0xfc, 0x2b, 0x01, 0x1f, 0xb8, 0x8c, 0x01, 0x74, 0x14, 0x83, 0xe3, 0xbf, 0xfe, 0x45, 0x5b, 0xda, 0xb1, 0xae, 0x35, 0xd0, 0xbb, 0x53, 0xa3, 0xc0, 0x0d, 0x40, 0x6d, 0x88, 0x36},
						Namespace: []byte{0x3a, 0x1a, 0x82, 0xd1, 0xad, 0x9f, 0x89, 0x6b, 0x59, 0x8e, 0xce, 0x45, 0xbc, 0x6f, 0x61, 0x34, 0x81, 0x7b, 0x7e, 0x2f, 0xa4, 0xd7, 0x15, 0xaf, 0x28, 0x15, 0xc0, 0x3e, 0x21, 0xfc, 0xcb, 0x3a, 0x38, 0x60, 0x96, 0xc7, 0xac, 0xe6, 0x56, 0xf2, 0xb7, 0x40, 0x4e, 0x9e, 0xb4, 0xbf, 0x96},
						Template: lockboxv1.LockboxSecretTemplate{
							LockboxSecretTemplateMetadata: lockboxv1.LockboxSecretTemplateMetadata{
								Labels: map[string]string{
									"type": "secret",
								},
								Annotations: map[string]string{
									"wave": "ignore",
								},
							},
						},
						Data: map[string][]byte{
							"test": {0x57, 0x17, 0x83, 0x22, 0x4c, 0x54, 0x1a, 0xb8, 0x83, 0x86, 0xc6, 0x15, 0xed, 0x23, 0x10, 0x58, 0x1d, 0xbc, 0x20, 0x47, 0xb4, 0x2a, 0x7f, 0xf6, 0xda, 0x4e, 0xa4, 0x88, 0x6b, 0x54, 0xed, 0xf6, 0xa3, 0x21, 0x73, 0xda, 0xca, 0x2b, 0xf7, 0x88, 0x13, 0xaa, 0xc2, 0xef},
						},
					},
				},
			},
			expected: &corev1.Secret{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "example",
					Namespace: "default",
					Labels: map[string]string{
						"type": "secret",
					},
					Annotations: map[string]string{
						"wave": "ignore",
					},
					OwnerReferences: []metav1.OwnerReference{
						{
							APIVersion:         "lockbox.k8s.cloudflare.com/v1",
							Kind:               "Lockbox",
							Name:               "example",
							Controller:         ptr.To(true),
							BlockOwnerDeletion: ptr.To(true),
						},
					},
				},
				Type: corev1.SecretTypeOpaque,
				Data: map[string][]byte{
					"test": []byte("test"),
				},
			},
		},
		{
			name:        "avoids updating secrets owned by other controllers",
			lockboxName: "example",
			resources: []client.Object{
				&corev1.Secret{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "example",
						Namespace: "default",
						OwnerReferences: []metav1.OwnerReference{
							{
								APIVersion:         "v1",
								Kind:               "ConfigMap",
								Name:               "example",
								UID:                "deadbeef",
								Controller:         ptr.To(true),
								BlockOwnerDeletion: ptr.To(true),
							},
						},
					},
					Data: map[string][]byte{
						"test":  []byte("test"),
						"test1": []byte("test1"),
					},
				},
				&lockboxv1.Lockbox{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "example",
						Namespace: "default",
					},
					Spec: lockboxv1.LockboxSpec{
						Sender:    []byte{0x74, 0xbd, 0xd8, 0x82, 0xf7, 0xd5, 0x87, 0xde, 0x08, 0x79, 0xf0, 0x9b, 0x35, 0x15, 0xf5, 0x2d, 0x1f, 0xb0, 0x26, 0xb3, 0x20, 0xe1, 0xe1, 0xd8, 0x5c, 0x5a, 0x0e, 0x1d, 0xfb, 0x80, 0x87, 0x23},
						Peer:      []byte{0x6a, 0x42, 0xb9, 0xfc, 0x2b, 0x01, 0x1f, 0xb8, 0x8c, 0x01, 0x74, 0x14, 0x83, 0xe3, 0xbf, 0xfe, 0x45, 0x5b, 0xda, 0xb1, 0xae, 0x35, 0xd0, 0xbb, 0x53, 0xa3, 0xc0, 0x0d, 0x40, 0x6d, 0x88, 0x36},
						Namespace: []byte{0x3a, 0x1a, 0x82, 0xd1, 0xad, 0x9f, 0x89, 0x6b, 0x59, 0x8e, 0xce, 0x45, 0xbc, 0x6f, 0x61, 0x34, 0x81, 0x7b, 0x7e, 0x2f, 0xa4, 0xd7, 0x15, 0xaf, 0x28, 0x15, 0xc0, 0x3e, 0x21, 0xfc, 0xcb, 0x3a, 0x38, 0x60, 0x96, 0xc7, 0xac, 0xe6, 0x56, 0xf2, 0xb7, 0x40, 0x4e, 0x9e, 0xb4, 0xbf, 0x96},
						Data: map[string][]byte{
							"test": {0x57, 0x17, 0x83, 0x22, 0x4c, 0x54, 0x1a, 0xb8, 0x83, 0x86, 0xc6, 0x15, 0xed, 0x23, 0x10, 0x58, 0x1d, 0xbc, 0x20, 0x47, 0xb4, 0x2a, 0x7f, 0xf6, 0xda, 0x4e, 0xa4, 0x88, 0x6b, 0x54, 0xed, 0xf6, 0xa3, 0x21, 0x73, 0xda, 0xca, 0x2b, 0xf7, 0x88, 0x13, 0xaa, 0xc2, 0xef},
						},
					},
				},
			},
			expected: &corev1.Secret{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "example",
					Namespace: "default",
					OwnerReferences: []metav1.OwnerReference{
						{
							APIVersion:         "v1",
							Kind:               "ConfigMap",
							Name:               "example",
							Controller:         ptr.To(true),
							BlockOwnerDeletion: ptr.To(true),
						},
					},
				},
				Type: corev1.SecretTypeOpaque,
				Data: map[string][]byte{
					"test":  []byte("test"),
					"test1": []byte("test1"),
				},
			},
		},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			setup(t, tc)
			run(t, tc)
		})
	}

}


================================================
FILE: pkg/lockbox-controller/secretreconciler_test.go
================================================
package controller_test

import (
	"context"
	"testing"

	lockboxv1 "github.com/cloudflare/lockbox/pkg/apis/lockbox.k8s.cloudflare.com/v1"
	controller "github.com/cloudflare/lockbox/pkg/lockbox-controller"
	"github.com/kevinburke/nacl"
	"gotest.tools/v3/assert"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"
	clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

func TestSecretReconciler(t *testing.T) {
	type testCase struct {
		name        string
		lockboxName string
		resources   []client.Object
		expected    *corev1.Secret
		expectedErr string
	}

	run := func(t *testing.T, tc testCase) {
		scheme := runtime.NewScheme()
		assert.NilError(t, corev1.AddToScheme(scheme))
		assert.NilError(t, lockboxv1.AddToScheme(scheme))

		client := clientfake.NewClientBuilder().
			WithObjects(tc.resources...).
			WithScheme(scheme).
			Build()

		pubKey, priKey, err := loadKeypair(t, "6a42b9fc2b011fb88c01741483e3bffe455bdab1ae35d0bb53a3c00d406d8836", "252173f975f0a0ddb198a7e5958c074203a0e9f44275e0b840f95d456c4acc2e")
		assert.NilError(t, err)

		lsn := types.NamespacedName{Name: tc.lockboxName, Namespace: "example"}
		sr := controller.NewSecretReconciler(pubKey, priKey, controller.WithClient(client))

		_, err = reconcile.AsReconciler(client, sr).Reconcile(context.Background(), reconcile.Request{NamespacedName: lsn})
		if tc.expectedErr != "" {
			assert.ErrorContains(t, err, tc.expectedErr)
		} else {
			assert.NilError(t, err)
		}

		actual := &corev1.Secret{}
		err = client.Get(context.Background(), lsn, actual)

		if tc.expected == nil {
			assert.Assert(t, apierrors.IsNotFound(err))
			return
		}

		assert.NilError(t, err)
		assert.DeepEqual(t, actual, tc.expected)
	}

	testCases := []testCase{
		{
			name:        "new lockbox",
			lockboxName: "example",
			resources: []client.Object{
				&lockboxv1.Lockbox{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "example",
						Namespace: "example",
						Labels: map[string]string{
							"type": "lockbox",
						},
						Annotations: map[string]string{
							"helm.sh/hook": "pre-install",
						},
					},
					Spec: lockboxv1.LockboxSpec{
						Sender:    []byte{0xb2, 0xa3, 0xf, 0x85, 0xa, 0x58, 0xcf, 0x94, 0x4c, 0x62, 0x37, 0xd4, 0xef, 0xf5, 0xed, 0x11, 0x52, 0xfa, 0x1b, 0xc3, 0xb0, 0x4d, 0x27, 0xd5, 0x58, 0x67, 0x61, 0x67, 0xe0, 0x10, 0xb1, 0x5c},
						Peer:      []byte{0x6a, 0x42, 0xb9, 0xfc, 0x2b, 0x1, 0x1f, 0xb8, 0x8c, 0x1, 0x74, 0x14, 0x83, 0xe3, 0xbf, 0xfe, 0x45, 0x5b, 0xda, 0xb1, 0xae, 0x35, 0xd0, 0xbb, 0x53, 0xa3, 0xc0, 0xd, 0x40, 0x6d, 0x88, 0x36},
						Namespace: []byte{0x4d, 0xa0, 0x73, 0x8b, 0x95, 0xc3, 0xd4, 0x64, 0xe9, 0xab, 0xd, 0xb7, 0x1e, 0x5, 0x10, 0xed, 0x4c, 0x2f, 0x8a, 0x66, 0x6d, 0xec, 0x7c, 0x5d, 0x9b, 0xa7, 0xb7, 0x88, 0x49, 0x8a, 0xb9, 0x7f, 0xf0, 0x30, 0xe0, 0xad, 0x49, 0x7c, 0x3f, 0xe3, 0x1c, 0x2e, 0xe9, 0xb1, 0x2a, 0x70, 0x28},
						Template: lockboxv1.LockboxSecretTemplate{
							LockboxSecretTemplateMetadata: lockboxv1.LockboxSecretTemplateMetadata{
								Labels: map[string]string{
									"type": "secret",
								},
								Annotations: map[string]string{
									"wave": "ignore",
								},
							},
							Type: corev1.SecretTypeOpaque,
						},
						Data: map[string][]byte{
							"test":  {0x7b, 0xca, 0x32, 0x90, 0xf7, 0x97, 0x3b, 0x6, 0xfb, 0x7c, 0xdc, 0x3a, 0x25, 0x82, 0x29, 0xdf, 0x9d, 0x1e, 0x46, 0x8d, 0xd4, 0x99, 0x49, 0x2, 0x63, 0x56, 0x54, 0x64, 0xae, 0x9e, 0xf2, 0xc0, 0x35, 0xf5, 0xf1, 0xcb, 0x67, 0xb7, 0xe2, 0xb1, 0x14, 0x42, 0x71, 0xc},
							"test1": {0x2c, 0x68, 0xed, 0x53, 0x55, 0x55, 0xe2, 0x2d, 0x71, 0x96, 0x85, 0xfd, 0xdb, 0x93, 0x1e, 0x77, 0x91, 0x2d, 0x76, 0xba, 0xae, 0x46, 0x30, 0x9e, 0xb6, 0x65, 0xa2, 0x49, 0xfe, 0x78, 0xc0, 0xcb, 0x6d, 0xf, 0xa8, 0xeb, 0xa8, 0xfc, 0xc0, 0xa0, 0xdc, 0x4, 0x16, 0x7, 0xa0},
						},
					},
				},
			},
			expected: &corev1.Secret{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "example",
					Namespace: "example",
					Labels: map[string]string{
						"type": "secret",
					},
					Annotations: map[string]string{
						"wave": "ignore",
					},
					ResourceVersion: "1",
					OwnerReferences: []metav1.OwnerReference{
						{
							APIVersion:         "lockbox.k8s.cloudflare.com/v1",
							Kind:               "Lockbox",
							Name:               "example",
							Controller:         ptr.To(true),
							BlockOwnerDeletion: ptr.To(true),
						},
					},
				},
				Type: corev1.SecretTypeOpaque,
				Data: map[string][]byte{
					"test":  []byte("test"),
					"test1": []byte("test1"),
				},
			},
		},
		{
			name:        "update lockbox secret",
			lockboxName: "example",
			resources: []client.Object{
				&lockboxv1.Lockbox{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "example",
						Namespace: "example",
					},
					Spec: lockboxv1.LockboxSpec{
						Sender:    []byte{0xa, 0xda, 0x33, 0xf3, 0x48, 0xad, 0xb6, 0x4c, 0xaa, 0x6, 0x50, 0xc1, 0xe1, 0xa6, 0xeb, 0x49, 0x13, 0xe0, 0x53, 0xdf, 0xde, 0x44, 0x72, 0xd6, 0xe2, 0x51, 0x94, 0xee, 0xcb, 0xba, 0xc1, 0x4},
						Peer:      []byte{0x6a, 0x42, 0xb9, 0xfc, 0x2b, 0x1, 0x1f, 0xb8, 0x8c, 0x1, 0x74, 0x14, 0x83, 0xe3, 0xbf, 0xfe, 0x45, 0x5b, 0xda, 0xb1, 0xae, 0x35, 0xd0, 0xbb, 0x53, 0xa3, 0xc0, 0xd, 0x40, 0x6d, 0x88, 0x36},
						Namespace: []byte{0xa7, 0x4c, 0x72, 0x7a, 0x71, 0x1d, 0x98, 0x32, 0xa, 0x3, 0xbe, 0xe5, 0x9d, 0xd4, 0x8c, 0x39, 0x3, 0x42, 0x9c, 0x5e, 0xeb, 0x6d, 0x95, 0x46, 0x5c, 0x10, 0x62, 0xa3, 0xa7, 0xfb, 0xee, 0x19, 0xcb, 0x98, 0xbf, 0xc1, 0x19, 0x66, 0x6a, 0x77, 0x76, 0x22, 0x17, 0x8f, 0xa5, 0x24, 0x8e},
						Data: map[string][]byte{
							"updated": {0x78, 0x70, 0x68, 0xae, 0x9f, 0xf5, 0xed, 0x60, 0x74, 0x14, 0x6a, 0xc5, 0xc3, 0xb, 0xe2, 0xaa, 0x20, 0x68, 0x7a, 0xfb, 0xa6, 0x6a, 0x38, 0xc2, 0x20, 0x73, 0xb5, 0x45, 0x9f, 0x9, 0xf0, 0x15, 0xd1, 0x5c, 0x16, 0x51, 0x50, 0xaa, 0xea, 0x68, 0x3a, 0x95, 0xe6},
						},
						Template: lockboxv1.LockboxSecretTemplate{
							Type: corev1.SecretTypeOpaque,
						},
					},
				},
				&corev1.Secret{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "example",
						Namespace: "example",
						Labels: map[string]string{
							"type": "secret",
						},
						Annotations: map[string]string{
							"wave": "ignore",
						},
						ResourceVersion: "1",
						OwnerReferences: []metav1.OwnerReference{
							{
								APIVersion:         "lockbox.k8s.cloudflare.com/v1",
								Kind:               "Lockbox",
								Name:               "example",
								Controller:         ptr.To(true),
								BlockOwnerDeletion: ptr.To(true),
							},
						},
					},
					Type: corev1.SecretTypeOpaque,
					Data: map[string][]byte{
						"test":  []byte("test"),
						"test1": []byte("test1"),
					},
				},
			},
			expected: &corev1.Secret{
				ObjectMeta: metav1.ObjectMeta{
					Name:            "example",
					Namespace:       "example",
					ResourceVersion: "2",
					OwnerReferences: []metav1.OwnerReference{
						{
							APIVersion:         "lockbox.k8s.cloudflare.com/v1",
							Kind:               "Lockbox",
							Name:               "example",
							Controller:         ptr.To(true),
							BlockOwnerDeletion: ptr.To(true),
						},
					},
				},
				Type: corev1.SecretTypeOpaque,
				Data: map[string][]byte{
					"updated": []byte("yep"),
				},
			},
		},
		{
			name:        "secret conflict",
			lockboxName: "example",
			resources: []client.Object{
				&lockboxv1.Lockbox{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "example",
						Namespace: "example",
					},
					Spec: lockboxv1.LockboxSpec{
						Sender:    []byte{0xa, 0xda, 0x33, 0xf3, 0x48, 0xad, 0xb6, 0x4c, 0xaa, 0x6, 0x50, 0xc1, 0xe1, 0xa6, 0xeb, 0x49, 0x13, 0xe0, 0x53, 0xdf, 0xde, 0x44, 0x72, 0xd6, 0xe2, 0x51, 0x94, 0xee, 0xcb, 0xba, 0xc1, 0x4},
						Peer:      []byte{0x6a, 0x42, 0xb9, 0xfc, 0x2b, 0x1, 0x1f, 0xb8, 0x8c, 0x1, 0x74, 0x14, 0x83, 0xe3, 0xbf, 0xfe, 0x45, 0x5b, 0xda, 0xb1, 0xae, 0x35, 0xd0, 0xbb, 0x53, 0xa3, 0xc0, 0xd, 0x40, 0x6d, 0x88, 0x36},
						Namespace: []byte{0xa7, 0x4c, 0x72, 0x7a, 0x71, 0x1d, 0x98, 0x32, 0xa, 0x3, 0xbe, 0xe5, 0x9d, 0xd4, 0x8c, 0x39, 0x3, 0x42, 0x9c, 0x5e, 0xeb, 0x6d, 0x95, 0x46, 0x5c, 0x10, 0x62, 0xa3, 0xa7, 0xfb, 0xee, 0x19, 0xcb, 0x98, 0xbf, 0xc1, 0x19, 0x66, 0x6a, 0x77, 0x76, 0x22, 0x17, 0x8f, 0xa5, 0x24, 0x8e},
						Data: map[string][]byte{
							"updated": {0x78, 0x70, 0x68, 0xae, 0x9f, 0xf5, 0xed, 0x60, 0x74, 0x14, 0x6a, 0xc5, 0xc3, 0xb, 0xe2, 0xaa, 0x20, 0x68, 0x7a, 0xfb, 0xa6, 0x6a, 0x38, 0xc2, 0x20, 0x73, 0xb5, 0x45, 0x9f, 0x9, 0xf0, 0x15, 0xd1, 0x5c, 0x16, 0x51, 0x50, 0xaa, 0xea, 0x68, 0x3a, 0x95, 0xe6},
						},
					},
				},
				&corev1.Secret{
					ObjectMeta: metav1.ObjectMeta{
						Name:            "example",
						Namespace:       "example",
						ResourceVersion: "1",
						OwnerReferences: []metav1.OwnerReference{
							{
								APIVersion:         "bitnami.com/v1alpha1",
								Kind:               "SealedSecret",
								Name:               "example",
								Controller:         ptr.To(true),
								BlockOwnerDeletion: ptr.To(true),
							},
						},
					},
					Type: corev1.SecretTypeOpaque,
					Data: map[string][]byte{
						"test":  []byte("test"),
						"test1": []byte("test1"),
					},
				},
			},
			expected: &corev1.Secret{
				ObjectMeta: metav1.ObjectMeta{
					Name:            "example",
					Namespace:       "example",
					ResourceVersion: "1",
					OwnerReferences: []metav1.OwnerReference{
						{
							APIVersion:         "bitnami.com/v1alpha1",
							Kind:               "SealedSecret",
							Name:               "example",
							Controller:         ptr.To(true),
							BlockOwnerDeletion: ptr.To(true),
						},
					},
				},
				Type: corev1.SecretTypeOpaque,
				Data: map[string][]byte{
					"test":  []byte("test"),
					"test1": []byte("test1"),
				},
			},
			expectedErr: "already owned by another SealedSecret controller example",
		},
		{
			name:        "docker-registry secret",
			lockboxName: "example",
			resources: []client.Object{
				&lockboxv1.Lockbox{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "example",
						Namespace: "example",
					},
					Spec: lockboxv1.LockboxSpec{
						Sender:    []byte{0x64, 0x22, 0x82, 0xe9, 0x35, 0x2a, 0x36, 0x7a, 0x40, 0x75, 0xd5, 0x14, 0xa6, 0x24, 0xef, 0xe3, 0x59, 0xda, 0xf5, 0xe9, 0xbe, 0xc8, 0x2d, 0x88, 0xb1, 0x17, 0xe8, 0xf2, 0x99, 0xb0, 0x9f, 0x71},
						Peer:      []byte{0x6a, 0x42, 0xb9, 0xfc, 0x2b, 0x1, 0x1f, 0xb8, 0x8c, 0x1, 0x74, 0x14, 0x83, 0xe3, 0xbf, 0xfe, 0x45, 0x5b, 0xda, 0xb1, 0xae, 0x35, 0xd0, 0xbb, 0x53, 0xa3, 0xc0, 0xd, 0x40, 0x6d, 0x88, 0x36},
						Namespace: []byte{0xd3, 0x1c, 0xc6, 0x29, 0x65, 0xac, 0xd6, 0x5, 0x3a, 0x60, 0xe1, 0x7c, 0xf8, 0xb9, 0x7, 0xdd, 0xdb, 0xf0, 0x82, 0xab, 0x90, 0x38, 0x7, 0x56, 0x72, 0x68, 0xef, 0x56, 0x3b, 0xae, 0x13, 0x16, 0x7e, 0x3e, 0xf6, 0xaf, 0xb4, 0x7b, 0x10, 0xed, 0x77, 0x29, 0xae, 0xcb, 0x96, 0x7f, 0xc9},
						Data: map[string][]byte{
							".dockerconfigjson": {0x98, 0x39, 0x7b, 0x93, 0x7a, 0xb6, 0x4, 0xc, 0xb8, 0x52, 0xf0, 0x97, 0x2e, 0x74, 0xed, 0xd6, 0x41, 0x7a, 0x7d, 0x20, 0xda, 0x35, 0x2d, 0xdf, 0x2b, 0x94, 0x9f, 0x78, 0x78, 0xd4, 0x29, 0x30, 0x6d, 0xbf, 0x9c, 0x59, 0x9f, 0xb4, 0x47, 0x5e, 0x10, 0x4a, 0xd2, 0xf, 0xd8, 0x77, 0x7d, 0x8, 0x11, 0x36, 0x41, 0xa9, 0xb2, 0x77, 0xac, 0xd9, 0xa3, 0x8, 0x81, 0x0, 0x6, 0x34, 0xde, 0x3e, 0xfc, 0x38, 0x4c, 0xa4, 0x27, 0xff, 0x1f, 0x67, 0x8, 0xef, 0x6, 0xff, 0x31, 0x80, 0xd, 0x4e, 0xcf, 0x6c, 0xec, 0x79, 0x78, 0x7d, 0x9f, 0x5b, 0x34, 0xe4, 0x5a, 0x44, 0x49, 0x57, 0xfd, 0xeb, 0x43, 0xd4, 0x4e, 0xe5, 0x15, 0xbc, 0xa8, 0x5a, 0x86, 0xd, 0xb9, 0xaa, 0x45, 0x6c, 0x4b, 0x17, 0x66, 0x13, 0xb2, 0x8c, 0x46, 0x7e, 0xdc, 0xe, 0x21, 0x54, 0x39, 0x27, 0xa3, 0x93, 0x52, 0x46, 0xa1, 0x71, 0x21, 0x8e, 0x27, 0x62, 0x6b, 0x86, 0xa6, 0xe4, 0x98, 0xc4, 0xff, 0x8, 0xed, 0xba, 0x4d, 0xa1, 0xfa, 0x53, 0x25, 0xb7, 0x29, 0x20, 0x1a, 0xab, 0x4a, 0xf5, 0x99, 0x99, 0x6a, 0x9d, 0xb8, 0x96, 0x28, 0x9b, 0x6a, 0xda, 0xb8, 0xee, 0x9c, 0x5f, 0xc1, 0x91, 0x0, 0x38, 0x84, 0x90, 0xdf, 0xbd, 0x9a, 0x1b, 0x9e, 0xd6, 0xe4, 0x3d},
						},
						Template: lockboxv1.LockboxSecretTemplate{
							Type: corev1.SecretTypeDockerConfigJson,
						},
					},
				},
			},
			expected: &corev1.Secret{
				ObjectMeta: metav1.ObjectMeta{
					Name:            "example",
					Namespace:       "example",
					ResourceVersion: "1",
					OwnerReferences: []metav1.OwnerReference{
						{
							APIVersion:         "lockbox.k8s.cloudflare.com/v1",
							Kind:               "Lockbox",
							Name:               "example",
							Controller:         ptr.To(true),
							BlockOwnerDeletion: ptr.To(true),
						},
					},
				},
				Type: corev1.SecretTypeDockerConfigJson,
				Data: map[string][]byte{
					".dockerconfigjson": {0x65, 0x79, 0x4a, 0x68, 0x64, 0x58, 0x52, 0x6f, 0x63, 0x79, 0x49, 0x36, 0x65, 0x79, 0x4a, 0x6b, 0x62, 0x32, 0x4e, 0x72, 0x5a, 0x58, 0x49, 0x75, 0x5a, 0x58, 0x68, 0x68, 0x62, 0x58, 0x42, 0x73, 0x5a, 0x53, 0x35, 0x6a, 0x62, 0x32, 0x30, 0x69, 0x4f, 0x6e, 0x73, 0x69, 0x56, 0x58, 0x4e, 0x6c, 0x63, 0x6d, 0x35, 0x68, 0x62, 0x57, 0x55, 0x69, 0x4f, 0x69, 0x4a, 0x71, 0x62, 0x32, 0x56, 0x6b, 0x5a, 0x58, 0x5a, 0x6c, 0x62, 0x47, 0x39, 0x77, 0x5a, 0x58, 0x49, 0x69, 0x4c, 0x43, 0x4a, 0x51, 0x59, 0x58, 0x4e, 0x7a, 0x64, 0x32, 0x39, 0x79, 0x5a, 0x43, 0x49, 0x36, 0x49, 0x6e, 0x42, 0x68, 0x63, 0x33, 0x4e, 0x33, 0x62, 0x33, 0x4a, 0x6b, 0x49, 0x69, 0x77, 0x69, 0x52, 0x57, 0x31, 0x68, 0x61, 0x57, 0x77, 0x69, 0x4f, 0x69, 0x4a, 0x71, 0x62, 0x32, 0x56, 0x41, 0x5a, 0x58, 0x68, 0x68, 0x62, 0x58, 0x42, 0x73, 0x5a, 0x53, 0x35, 0x6a, 0x62, 0x32, 0x30, 0x69, 0x66, 0x58, 0x31, 0x39},
				},
			},
		},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			run(t, tc)
		})
	}
}

func loadKeypair(t *testing.T, pub, pri string) (pubKey, priKey nacl.Key, err error) {
	t.Helper()

	pubKey, err = nacl.Load(pub)
	if err != nil {
		return
	}

	priKey, err = nacl.Load(pri)
	return
}


================================================
FILE: pkg/lockbox-server/serve.go
================================================
package server

import (
	"net/http"

	"github.com/kevinburke/nacl"
)

// PublicKey creates an HTTP handler that responses with the specified public key
// as binary data.
func PublicKey(pubKey nacl.Key) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/octet-stream")
		_, _ = w.Write(pubKey[:])
	})
}


================================================
FILE: pkg/statemetrics/collector.go
================================================
package statemetrics

import (
	"sync"

	"github.com/prometheus/client_golang/prometheus"
	"k8s.io/apimachinery/pkg/types"
)

// Kubernetes is a metric tracking a numerical value that can arbitrary go up and down.
type Kubernetes interface {
	prometheus.Metric
	prometheus.Collector

	// Set sets the tracked number to an arbitrary value.
	Set(float64)
}

// kubernetes implements Kubernetes.
type kubernetes struct {
	prometheus.Metric
	values []string
	desc   *prometheus.Desc
}

// Set creates a new constant metric for this numerical value
func (k *kubernetes) Set(val float64) {
	k.Metric = prometheus.MustNewConstMetric(k.desc, prometheus.GaugeValue, val, k.values...)
}

// Describe implements Collector
func (k *kubernetes) Describe(ch chan<- *prometheus.Desc) {
	ch <- k.desc
}

// Collect implements Collector
func (k *kubernetes) Collect(ch chan<- prometheus.Metric) {
	ch <- k.Metric
}

// KubernetesVec is a Collector that bundles a set of Kubernetes metrics that all share the same Desc,
// but have different values for their variable labels. This is used if you want to count the same
// thing partitioned by various dimensions (e.g., namespaces, types). Create instances with
// NewKubernetesVec.
type KubernetesVec struct {
	desc    *prometheus.Desc
	metrics map[types.UID]Kubernetes
	mu      sync.Mutex
}

// KubernetesOpts is an alias for Opts.
type KubernetesOpts prometheus.Opts

// NewKubernetesVec creates a new KubernetesVec based on the provided KubernetesOpts and partitioned
// by the given label names.
func NewKubernetesVec(opts KubernetesOpts, labelNames []string) *KubernetesVec {
	desc := prometheus.NewDesc(
		prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
		opts.Help,
		labelNames,
		opts.ConstLabels,
	)

	return &KubernetesVec{
		desc:    desc,
		metrics: make(map[types.UID]Kubernetes),
	}
}

// WithLabelValues returns the Kubernetes metric for the given slice of label values (in the same order
// as the variable labels).
//
// Consecutive calls for the same uid replace earlier metrics.
func (v *KubernetesVec) WithLabelValues(uid types.UID, lvs ...string) Kubernetes {
	k := &kubernetes{
		values: lvs,
		desc:   v.desc,
	}
	k.Set(0)

	v.mu.Lock()
	v.metrics[uid] = k
	v.mu.Unlock()

	return k
}

// Delete deletes the metric stored for this uid.
func (v *KubernetesVec) Delete(uid types.UID) {
	v.mu.Lock()
	defer v.mu.Unlock()

	delete(v.metrics, uid)
}

// Describe implements Collector.
func (v *KubernetesVec) Describe(ch chan<- *prometheus.Desc) {
	ch <- v.desc
}

// Collect implements Collector.
func (v *KubernetesVec) Collect(ch chan<- prometheus.Metric) {
	v.mu.Lock()
	defer v.mu.Unlock()

	for _, metric := range v.metrics {
		ch <- metric
	}
}

// LabelsVec is a Collector that bundles a set of unchecked Kubernetes metrics that all share the
// same Desc, but have different labels. This is used if you want to count the same thing
// partitioned by unbounded dimensions (e.g., Kubernetes labels). Create instances with
// NewLabelsVec.
type LabelsVec struct {
	opts    KubernetesOpts
	metrics map[types.UID]Kubernetes
	mu      sync.Mutex
}

// NewLabelsVec creates a new LabelsVec based on the provided KubernetesOpts.
func NewLabelsVec(opts KubernetesOpts) *LabelsVec {
	return &LabelsVec{
		opts:    opts,
		metrics: make(map[types.UID]Kubernetes),
	}
}

// With returns the Kubernetes metric for the given Labels map.
//
// Consecutive calls with the same uid replace earlier metrics.
func (v *LabelsVec) With(uid types.UID, l prometheus.Labels) Kubernetes {
	labels := make([]string, 0, len(l))
	values := make([]string, 0, len(l))

	for label, value := range l {
		labels = append(labels, label)
		values = append(values, value)
	}

	desc := prometheus.NewDesc(
		prometheus.BuildFQName(v.opts.Namespace, v.opts.Subsystem, v.opts.Name),
		v.opts.Help,
		labels,
		v.opts.ConstLabels,
	)

	k := &kubernetes{
		values: values,
		desc:   desc,
	}
	k.Set(0)

	v.mu.Lock()
	v.metrics[uid] = k
	v.mu.Unlock()

	return k
}

// Delete deletes the metric stored for this uid.
func (v *LabelsVec) Delete(uid types.UID) {
	v.mu.Lock()
	defer v.mu.Unlock()

	delete(v.metrics, uid)
}

// Describe implements Collector. No Desc are sent on the channel to make this
// an unchecked collector.
func (v *LabelsVec) Describe(chan<- *prometheus.Desc) {}

// Collect implements Collector.
func (v *LabelsVec) Collect(ch chan<- prometheus.Metric) {
	v.mu.Lock()
	defer v.mu.Unlock()

	for _, metric := range v.metrics {
		ch <- metric
	}
}


================================================
FILE: pkg/statemetrics/handler.go
================================================
package statemetrics

import (
	"context"
	"encoding/hex"

	lockboxv1 "github.com/cloudflare/lockbox/pkg/apis/lockbox.k8s.cloudflare.com/v1"
	"k8s.io/client-go/util/workqueue"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/event"
	"sigs.k8s.io/controller-runtime/pkg/handler"
)

// StateMetricProxy updates state metrics by intercepting events as they are passed to event handlers.
type StateMetricProxy struct {
	enqueuer handler.EventHandler

	info            *KubernetesVec
	created         *KubernetesVec
	resourceVersion *KubernetesVec
	lbType          *KubernetesVec
	peerKey         *KubernetesVec
	labels          *LabelsVec
}

// NewStateMetricProxy returns a StateMetricsProxy. All metrics must be non-nil.
func NewStateMetricProxy(enqueuer handler.EventHandler, info, created, resourceVersion, lbType, peerKey *KubernetesVec, labels *LabelsVec) *StateMetricProxy {
	return &StateMetricProxy{
		enqueuer:        enqueuer,
		info:            info,
		created:         created,
		resourceVersion: resourceVersion,
		lbType:          lbType,
		peerKey:         peerKey,
		labels:          labels,
	}
}

// Create implements EventHandler.
func (s *StateMetricProxy) Create(ctx context.Context, evt event.CreateEvent, q workqueue.RateLimitingInterface) {
	s.updateWith(evt.Object)

	if s.enqueuer != nil {
		s.enqueuer.Create(ctx, evt, q)
	}
}

// Update implements EventHandler.
func (s *StateMetricProxy) Update(ctx context.Context, evt event.UpdateEvent, q workqueue.RateLimitingInterface) {
	s.updateWith(evt.ObjectNew)

	if s.enqueuer != nil {
		s.enqueuer.Update(ctx, evt, q)
	}
}

// Delete implements EventHandler.
func (s *StateMetricProxy) Delete(ctx context.Context, evt event.DeleteEvent, q workqueue.RateLimitingInterface) {
	uid := evt.Object.GetUID()

	s.info.Delete(uid)
	s.created.Delete(uid)
	s.resourceVersion.Delete(uid)
	s.lbType.Delete(uid)
	s.peerKey.Delete(uid)
	s.labels.Delete(uid)

	if s.enqueuer != nil {
		s.enqueuer.Delete(ctx, evt, q)
	}
}

// Generic implements EventHandler.
func (s *StateMetricProxy) Generic(ctx context.Context, evt event.GenericEvent, q workqueue.RateLimitingInterface) {
	if s.enqueuer != nil {
		s.enqueuer.Generic(ctx, evt, q)
	}
}

// updateWith updates the metrics for Create and Update handles.
func (s *StateMetricProxy) updateWith(obj client.Object) {
	namespace := obj.GetNamespace()
	lockbox := obj.GetName()
	uid := obj.GetUID()

	s.info.WithLabelValues(uid, namespace, lockbox).Set(1)
	creationTime := obj.GetCreationTimestamp()
	if !creationTime.IsZero() {
		s.created.WithLabelValues(uid, namespace, lockbox).Set(float64(creationTime.Unix()))
	}
	s.resourceVersion.WithLabelValues(uid, namespace, lockbox, obj.GetResourceVersion()).Set(1)

	if lb, ok := obj.(*lockboxv1.Lockbox); ok {
		s.lbType.WithLabelValues(uid, namespace, lockbox, string(lb.Spec.Template.Type)).Set(1)
		s.peerKey.WithLabelValues(uid, namespace, lockbox, hex.EncodeToString(lb.Spec.Peer)).Set(1)
	}

	promLabels := kubernetesLabelsToPrometheusLabels(obj.GetLabels())
	promLabels["namespace"] = namespace
	promLabels["lockbox"] = lockbox

	s.labels.With(uid, promLabels).Set(1)
}


================================================
FILE: pkg/statemetrics/handler_test.go
================================================
package statemetrics

import (
	"context"
	"strings"
	"testing"
	"time"

	lockboxv1 "github.com/cloudflare/lockbox/pkg/apis/lockbox.k8s.cloudflare.com/v1"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/testutil"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/event"
)

func TestStateMetricsProxy_Create(t *testing.T) {
	lb := &lockboxv1.Lockbox{
		ObjectMeta: metav1.ObjectMeta{
			Namespace:         "fizz",
			Name:              "buzz",
			UID:               "foobar",
			ResourceVersion:   "9001",
			CreationTimestamp: metav1.Date(2001, time.September, 9, 1, 46, 40, 0, time.UTC),
			Labels: map[string]string{
				"testing": "true",
			},
		},
		Spec: lockboxv1.LockboxSpec{
			Peer: []byte{0xDE, 0xAD, 0xBE, 0xEF},
			Template: lockboxv1.LockboxSecretTemplate{
				Type: "golang.org/testing",
			},
		},
	}
	info, created, resourceVersion, lbType, peerKey, labels := createMetricVectors(t)

	reg := prometheus.NewPedanticRegistry()
	reg.MustRegister(info, created, resourceVersion, lbType, peerKey, labels)

	evt := event.CreateEvent{Object: lb}

	handler := NewStateMetricProxy(nil, info, created, resourceVersion, lbType, peerKey, labels)
	handler.Create(context.Background(), evt, nil)

	expected := strings.NewReader(`
# HELP kube_lockbox_info Information about Lockbox
# TYPE kube_lockbox_info gauge
kube_lockbox_info{lockbox="buzz",namespace="fizz"} 1
# HELP kube_lockbox_created Unix creation timestamp
# TYPE kube_lockbox_created gauge
kube_lockbox_created{lockbox="buzz",namespace="fizz"} 1e9
# HELP kube_lockbox_resource_version Resource version representing a specific version of a Lockbox
# TYPE kube_lockbox_resource_version gauge
kube_lockbox_resource_version{lockbox="buzz",namespace="fizz",resource_version="9001"} 1
# HELP kube_lockbox_type Lockbox secret type
# TYPE kube_lockbox_type gauge
kube_lockbox_type{lockbox="buzz",namespace="fizz",type="golang.org/testing"} 1
# HELP kube_lockbox_peer Lockbox peer key
# TYPE kube_lockbox_peer gauge
kube_lockbox_peer{lockbox="buzz",namespace="fizz",peer="deadbeef"} 1
# HELP kube_lockbox_labels Kubernetes labels converted to Prometheus labels
# TYPE kube_lockbox_labels gauge
kube_lockbox_labels{label_testing="true",lockbox="buzz",namespace="fizz"} 1
`)

	if err := testutil.GatherAndCompare(reg, expected); err != nil {
		t.Error(err)
	}
}

func TestStateMetricsProxy_Update(t *testing.T) {
	old := &lockboxv1.Lockbox{
		ObjectMeta: metav1.ObjectMeta{
			Namespace:         "fizz",
			Name:              "buzz",
			UID:               "foobar",
			ResourceVersion:   "8999",
			CreationTimestamp: metav1.Now(),
			Labels: map[string]string{
				"testing": "false",
			},
		},
		Spec: lockboxv1.LockboxSpec{
			Peer: []byte{0x00},
			Template: lockboxv1.LockboxSecretTemplate{
				Type: "example.org/old",
			},
		},
	}
	lb := &lockboxv1.Lockbox{
		ObjectMeta: metav1.ObjectMeta{
			Namespace:         "fizz",
			Name:              "buzz",
			UID:               "foobar",
			ResourceVersion:   "9001",
			CreationTimestamp: metav1.Date(2001, time.September, 9, 1, 46, 40, 0, time.UTC),
			Labels: map[string]string{
				"testing": "true",
			},
		},
		Spec: lockboxv1.LockboxSpec{
			Peer: []byte{0xDE, 0xAD, 0xBE, 0xEF},
			Template: lockboxv1.LockboxSecretTemplate{
				Type: "golang.org/testing",
			},
		},
	}
	info, created, resourceVersion, lbType, peerKey, labels := createMetricVectors(t)

	reg := prometheus.NewPedanticRegistry()
	reg.MustRegister(info, created, resourceVersion, lbType, peerKey, labels)

	create := event.CreateEvent{Object: old}
	upd := event.UpdateEvent{
		ObjectOld: old,
		ObjectNew: lb,
	}

	handler := NewStateMetricProxy(nil, info, created, resourceVersion, lbType, peerKey, labels)
	handler.Create(context.Background(), create, nil)
	handler.Update(context.Background(), upd, nil)

	expected := strings.NewReader(`
# HELP kube_lockbox_info Information about Lockbox
# TYPE kube_lockbox_info gauge
kube_lockbox_info{lockbox="buzz",namespace="fizz"} 1
# HELP kube_lockbox_created Unix creation timestamp
# TYPE kube_lockbox_created gauge
kube_lockbox_created{lockbox="buzz",namespace="fizz"} 1e9
# HELP kube_lockbox_resource_version Resource version representing a specific version of a Lockbox
# TYPE kube_lockbox_resource_version gauge
kube_lockbox_resource_version{lockbox="buzz",namespace="fizz",resource_version="9001"} 1
# HELP kube_lockbox_type Lockbox secret type
# TYPE kube_lockbox_type gauge
kube_lockbox_type{lockbox="buzz",namespace="fizz",type="golang.org/testing"} 1
# HELP kube_lockbox_peer Lockbox peer key
# TYPE kube_lockbox_peer gauge
kube_lockbox_peer{lockbox="buzz",namespace="fizz",peer="deadbeef"} 1
# HELP kube_lockbox_labels Kubernetes labels converted to Prometheus labels
# TYPE kube_lockbox_labels gauge
kube_lockbox_labels{label_testing="true",lockbox="buzz",namespace="fizz"} 1
`)

	if err := testutil.GatherAndCompare(reg, expected); err != nil {
		t.Error(err)
	}
}

func TestStateMetricsProxy_Delete(t *testing.T) {
	lb := &lockboxv1.Lockbox{
		ObjectMeta: metav1.ObjectMeta{
			Namespace:         "fizz",
			Name:              "buzz",
			UID:               "foobar",
			ResourceVersion:   "9001",
			CreationTimestamp: metav1.Date(2001, time.September, 9, 1, 46, 40, 0, time.UTC),
			Labels: map[string]string{
				"testing": "true",
			},
		},
		Spec: lockboxv1.LockboxSpec{
			Peer: []byte{0xDE, 0xAD, 0xBE, 0xEF},
			Template: lockboxv1.LockboxSecretTemplate{
				Type: "golang.org/testing",
			},
		},
	}
	info, created, resourceVersion, lbType, peerKey, labels := createMetricVectors(t)

	reg := prometheus.NewPedanticRegistry()
	reg.MustRegister(info, created, resourceVersion, lbType, peerKey, labels)

	create := event.CreateEvent{Object: lb}
	deleted := event.DeleteEvent{
		Object:             lb,
		DeleteStateUnknown: false,
	}

	handler := NewStateMetricProxy(nil, info, created, resourceVersion, lbType, peerKey, labels)
	handler.Create(context.Background(), create, nil)
	handler.Delete(context.Background(), deleted, nil)

	expected := &strings.Reader{}

	if err := testutil.GatherAndCompare(reg, expected); err != nil {
		t.Error(err)
	}
}

func createMetricVectors(t *testing.T) (info, created, resourceVersion, lbType, peerKey *KubernetesVec, labels *LabelsVec) {
	info = NewKubernetesVec(KubernetesOpts{
		Name: "kube_lockbox_info",
		Help: "Information about Lockbox",
	}, []string{"namespace", "lockbox"})
	created = NewKubernetesVec(KubernetesOpts{
		Name: "kube_lockbox_created",
		Help: "Unix creation timestamp",
	}, []string{"namespace", "lockbox"})
	resourceVersion = NewKubernetesVec(KubernetesOpts{
		Name: "kube_lockbox_resource_version",
		Help: "Resource version representing a specific version of a Lockbox",
	}, []string{"namespace", "lockbox", "resource_version"})
	lbType = NewKubernetesVec(KubernetesOpts{
		Name: "kube_lockbox_type",
		Help: "Lockbox secret type",
	}, []string{"namespace", "lockbox", "type"})
	peerKey = NewKubernetesVec(KubernetesOpts{
		Name: "kube_lockbox_peer",
		Help: "Lockbox peer key",
	}, []string{"namespace", "lockbox", "peer"})
	labels = NewLabelsVec(KubernetesOpts{
		Name: "kube_lockbox_labels",
		Help: "Kubernetes labels converted to Prometheus labels",
	})

	return
}


================================================
FILE: pkg/statemetrics/labels.go
================================================
package statemetrics

import (
	"regexp"

	"github.com/prometheus/client_golang/prometheus"
)

var invalidLabelCharRE = regexp.MustCompile(`[^a-zA-Z0-9_]`)

// sanitizeLabel replaces non-alphanumeric characters with underscores.
func sanitizeLabel(l string) string {
	return invalidLabelCharRE.ReplaceAllString(l, "_")
}

// kubernetesLabelsToPrometheusLabels generates Prometheus-safe labels from
// the resource's Kubernetes labels.
func kubernetesLabelsToPrometheusLabels(labels map[string]string) prometheus.Labels {
	promLabels := map[string]string{}
	for l, v := range labels {
		promLabels["label_"+sanitizeLabel(l)] = v
	}

	return promLabels
}


================================================
FILE: pkg/statemetrics/labels_test.go
================================================
package statemetrics

import (
	"strings"
	"testing"
	"testing/quick"

	"github.com/prometheus/common/model"
)

const reservedLabelPrefix = "__"

func TestLabelsTransformation(t *testing.T) {
	f := func(labels map[string]string) bool {
		newLabels := kubernetesLabelsToPrometheusLabels(labels)

		for k := range newLabels {
			if !checkLabelName(k) {
				return false
			}
		}

		return true
	}

	if err := quick.Check(f, nil); err != nil {
		t.Error(err)
	}
}

func checkLabelName(l string) bool {
	return model.LabelName(l).IsValid() && !strings.HasPrefix(l, reservedLabelPrefix)
}


================================================
FILE: pkg/util/conditions/conditions.go
================================================
// Adapted from https://github.com/kubernetes-sigs/cluster-api/tree/v0.3.10/util/conditions
//
// Copyright 2020 The Kubernetes Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package conditions provides functions for setting status conditions on Lockbox resources
package conditions

import (
	"sort"
	"time"

	lockboxv1 "github.com/cloudflare/lockbox/pkg/apis/lockbox.k8s.cloudflare.com/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type Getter interface {
	GetConditions() []lockboxv1.Condition
}

type Setter interface {
	Getter
	SetConditions([]lockboxv1.Condition)
}

func FalseCondition(t lockboxv1.ConditionType, reason string, severity lockboxv1.ConditionSeverity, message string) *lockboxv1.Condition {
	return &lockboxv1.Condition{
		Type:     t,
		Status:   "False",
		Reason:   reason,
		Severity: severity,
		Message:  message,
	}
}

func TrueCondition(t lockboxv1.ConditionType) *lockboxv1.Condition {
	return &lockboxv1.Condition{
		Type:   t,
		Status: "True",
	}
}

func UnknownCondition(t lockboxv1.ConditionType, reason string, message string) *lockboxv1.Condition {
	return &lockboxv1.Condition{
		Type:    t,
		Status:  "Unknown",
		Reason:  reason,
		Message: message,
	}
}

func Get(from Getter, t lockboxv1.ConditionType) *lockboxv1.Condition {
	conditions := from.GetConditions()
	if conditions == nil {
		return nil
	}

	for _, condition := range conditions {
		if condition.Type == t {
			return &condition
		}
	}

	return nil
}

func Set(to Setter, condition *lockboxv1.Condition) {
	if to == nil || condition == nil {
		return
	}

	conditions := to.GetConditions()
	exists := false
	for i := range conditions {
		existingCondition := conditions[i]
		if existingCondition.Type == condition.Type {
			exists = true
			if !hasSameState(&existingCondition, condition) {
				condition.LastTransitionTime = metav1.NewTime(time.Now().UTC().Truncate(time.Second))
				conditions[i] = *condition
				break
			}
			condition.LastTransitionTime = existingCondition.LastTransitionTime
			break
		}
	}

	if !exists {
		if condition.LastTransitionTime.IsZero() {
			condition.LastTransitionTime = metav1.NewTime(time.Now().UTC().Truncate(time.Second))
		}
		conditions = append(conditions, *condition)
	}

	sort.Slice(conditions, func(i, j int) bool {
		return lexicographicLess(&conditions[i], &conditions[j])
	})

	to.SetConditions(conditions)
}

func lexicographicLess(i, j *lockboxv1.Condition) bool {
	return (i.Type == "Ready" || i.Type < j.Type) && j.Type != "Ready"
}

func hasSameState(i, j *lockboxv1.Condition) bool {
	return i.Type == j.Type &&
		i.Status == j.Status &&
		i.Reason == j.Reason &&
		i.Severity == j.Severity &&
		i.Message == j.Message
}


================================================
FILE: tools/tools.go
================================================
//go:build tools
// +build tools

package tools

import (
	_ "sigs.k8s.io/controller-tools/cmd/controller-gen"
)
Download .txt
gitextract_0jzthxzx/

├── .dockerignore
├── .github/
│   └── workflows/
│       ├── docker.yaml
│       ├── semgrep.yml
│       └── tests.yaml
├── .gitignore
├── LICENSE
├── Makefile
├── README.org
├── cmd/
│   ├── lockbox-controller/
│   │   ├── Dockerfile
│   │   ├── keypair.go
│   │   └── main.go
│   ├── lockbox-keypair/
│   │   └── main.go
│   └── locket/
│       └── main.go
├── deployment/
│   ├── crds/
│   │   └── lockbox.k8s.cloudflare.com_lockboxes.yaml
│   ├── manifests/
│   │   ├── deployment-lockbox.yaml
│   │   ├── namespace-lockbox.yaml
│   │   ├── service-lockbox.yaml
│   │   └── serviceaccount-lockbox.yaml
│   └── rbac/
│       ├── proxier.yaml
│       ├── role-binding.yaml
│       └── role.yaml
├── go.mod
├── go.sum
├── pkg/
│   ├── apis/
│   │   └── lockbox.k8s.cloudflare.com/
│   │       └── v1/
│   │           ├── groupversion_info.go
│   │           ├── lockbox.go
│   │           ├── lockbox_test.go
│   │           ├── types.go
│   │           └── zz_generated.deepcopy.go
│   ├── flagvar/
│   │   ├── enum.go
│   │   ├── enum_test.go
│   │   ├── file.go
│   │   ├── file_test.go
│   │   ├── tcp_addr.go
│   │   ├── tcp_addr_test.go
│   │   └── testdata/
│   │       └── file
│   ├── lockbox-controller/
│   │   ├── secretreconciler.go
│   │   ├── secretreconciler_suite_test.go
│   │   └── secretreconciler_test.go
│   ├── lockbox-server/
│   │   └── serve.go
│   ├── statemetrics/
│   │   ├── collector.go
│   │   ├── handler.go
│   │   ├── handler_test.go
│   │   ├── labels.go
│   │   └── labels_test.go
│   └── util/
│       └── conditions/
│           └── conditions.go
└── tools/
    └── tools.go
Download .txt
SYMBOL INDEX (127 symbols across 25 files)

FILE: cmd/lockbox-controller/keypair.go
  type kp (line 11) | type kp struct
  function KeyPairFromYAMLOrJSON (line 17) | func KeyPairFromYAMLOrJSON(r io.Reader) (pub, pri nacl.Key, err error) {

FILE: cmd/lockbox-controller/main.go
  function main (line 45) | func main() {

FILE: cmd/lockbox-keypair/main.go
  function main (line 12) | func main() {

FILE: cmd/locket/main.go
  function main (line 41) | func main() {
  function GetConfig (line 189) | func GetConfig() clientcmd.ClientConfig {
  function GetRemotePublicKey (line 200) | func GetRemotePublicKey(ctx context.Context, c kubernetes.Interface, ns,...

FILE: pkg/apis/lockbox.k8s.cloudflare.com/v1/groupversion_info.go
  function init (line 25) | func init() {

FILE: pkg/apis/lockbox.k8s.cloudflare.com/v1/lockbox.go
  constant keySize (line 10) | keySize = nacl.KeySize
  function NewFromSecret (line 14) | func NewFromSecret(secret corev1.Secret, namespace string, peer, pub, pr...
  method UnlockInto (line 51) | func (in *Lockbox) UnlockInto(secret *corev1.Secret, pri nacl.Key) error {
  type decryptSecretKeyError (line 75) | type decryptSecretKeyError struct
    method SecretKey (line 81) | func (e decryptSecretKeyError) SecretKey() string {
    method Unwrap (line 86) | func (e decryptSecretKeyError) Unwrap() error {
  method GetConditions (line 90) | func (in *Lockbox) GetConditions() []Condition {
  method SetConditions (line 94) | func (in *Lockbox) SetConditions(conditions []Condition) {

FILE: pkg/apis/lockbox.k8s.cloudflare.com/v1/lockbox_test.go
  function TestUnlock (line 15) | func TestUnlock(t *testing.T) {
  function TestUnlockErr (line 71) | func TestUnlockErr(t *testing.T) {
  function TestLockUnlock (line 115) | func TestLockUnlock(t *testing.T) {
  function loadKeypair (line 147) | func loadKeypair(t *testing.T, pub, pri string) (pubKey, priKey nacl.Key...

FILE: pkg/apis/lockbox.k8s.cloudflare.com/v1/types.go
  type Lockbox (line 15) | type Lockbox struct
  type LockboxSpec (line 29) | type LockboxSpec struct
  type LockboxSecretTemplate (line 53) | type LockboxSecretTemplate struct
  type LockboxSecretTemplateMetadata (line 60) | type LockboxSecretTemplateMetadata struct
  type LockboxStatus (line 77) | type LockboxStatus struct
  type Condition (line 84) | type Condition struct
  type ConditionType (line 116) | type ConditionType
  constant ReadyCondition (line 119) | ReadyCondition ConditionType = "Ready"
  type ConditionSeverity (line 123) | type ConditionSeverity
  constant ConditionSeverityError (line 126) | ConditionSeverityError   ConditionSeverity = "Error"
  constant ConditionSeverityWarning (line 127) | ConditionSeverityWarning ConditionSeverity = "Warning"
  constant ConditionSeverityInfo (line 128) | ConditionSeverityInfo    ConditionSeverity = "Info"
  constant ConditionSeverityNone (line 129) | ConditionSeverityNone    ConditionSeverity = ""
  type LockboxList (line 135) | type LockboxList struct

FILE: pkg/apis/lockbox.k8s.cloudflare.com/v1/zz_generated.deepcopy.go
  method DeepCopyInto (line 12) | func (in *Condition) DeepCopyInto(out *Condition) {
  method DeepCopy (line 18) | func (in *Condition) DeepCopy() *Condition {
  method DeepCopyInto (line 28) | func (in *Lockbox) DeepCopyInto(out *Lockbox) {
  method DeepCopy (line 37) | func (in *Lockbox) DeepCopy() *Lockbox {
  method DeepCopyObject (line 47) | func (in *Lockbox) DeepCopyObject() runtime.Object {
  method DeepCopyInto (line 55) | func (in *LockboxList) DeepCopyInto(out *LockboxList) {
  method DeepCopy (line 69) | func (in *LockboxList) DeepCopy() *LockboxList {
  method DeepCopyObject (line 79) | func (in *LockboxList) DeepCopyObject() runtime.Object {
  method DeepCopyInto (line 87) | func (in *LockboxSecretTemplate) DeepCopyInto(out *LockboxSecretTemplate) {
  method DeepCopy (line 93) | func (in *LockboxSecretTemplate) DeepCopy() *LockboxSecretTemplate {
  method DeepCopyInto (line 103) | func (in *LockboxSecretTemplateMetadata) DeepCopyInto(out *LockboxSecret...
  method DeepCopy (line 122) | func (in *LockboxSecretTemplateMetadata) DeepCopy() *LockboxSecretTempla...
  method DeepCopyInto (line 132) | func (in *LockboxSpec) DeepCopyInto(out *LockboxSpec) {
  method DeepCopy (line 169) | func (in *LockboxSpec) DeepCopy() *LockboxSpec {
  method DeepCopyInto (line 179) | func (in *LockboxStatus) DeepCopyInto(out *LockboxStatus) {
  method DeepCopy (line 191) | func (in *LockboxStatus) DeepCopy() *LockboxStatus {

FILE: pkg/flagvar/enum.go
  type Enum (line 11) | type Enum struct
    method Help (line 16) | func (e *Enum) Help() string {
    method Set (line 20) | func (e *Enum) Set(v string) error {
    method String (line 31) | func (e *Enum) String() string {

FILE: pkg/flagvar/enum_test.go
  function TestEnumString (line 10) | func TestEnumString(t *testing.T) {
  function TestEnumSet (line 43) | func TestEnumSet(t *testing.T) {

FILE: pkg/flagvar/file.go
  type File (line 8) | type File struct
    method Help (line 13) | func (f *File) Help() string {
    method Set (line 19) | func (f *File) Set(v string) error {
    method String (line 27) | func (f *File) String() string {
    method Type (line 36) | func (f *File) Type() string {

FILE: pkg/flagvar/file_test.go
  function TestFileString (line 12) | func TestFileString(t *testing.T) {
  function TestFileSet (line 45) | func TestFileSet(t *testing.T) {

FILE: pkg/flagvar/tcp_addr.go
  type TCPAddr (line 6) | type TCPAddr struct
    method Help (line 13) | func (t *TCPAddr) Help() string {
    method Set (line 19) | func (t *TCPAddr) Set(v string) error {
    method String (line 33) | func (t *TCPAddr) String() string {
    method Type (line 42) | func (t *TCPAddr) Type() string {

FILE: pkg/flagvar/tcp_addr_test.go
  function TestTCPAddrString (line 11) | func TestTCPAddrString(t *testing.T) {
  function TestTCPAddrSet (line 44) | func TestTCPAddrSet(t *testing.T) {

FILE: pkg/lockbox-controller/secretreconciler.go
  constant keySize (line 28) | keySize = nacl.KeySize
  type SecretReconcilerOption (line 31) | type SecretReconcilerOption
  type SecretReconciler (line 34) | type SecretReconciler struct
    method Reconcile (line 60) | func (s *SecretReconciler) Reconcile(ctx context.Context, lb *lockboxv...
    method reconcileExisting (line 138) | func (s *SecretReconciler) reconcileExisting(lb *lockboxv1.Lockbox, se...
  function NewSecretReconciler (line 44) | func NewSecretReconciler(pubKey, priKey nacl.Key, options ...SecretRecon...
  function WithRecorder (line 156) | func WithRecorder(r record.EventRecorder) SecretReconcilerOption {
  function WithClient (line 163) | func WithClient(c client.Client) SecretReconcilerOption {
  type decryptSecretKeyErrorer (line 171) | type decryptSecretKeyErrorer interface

FILE: pkg/lockbox-controller/secretreconciler_suite_test.go
  function TestMain (line 38) | func TestMain(m *testing.M) {
  function TestSuiteSecretReconciler (line 56) | func TestSuiteSecretReconciler(t *testing.T) {

FILE: pkg/lockbox-controller/secretreconciler_test.go
  function TestSecretReconciler (line 22) | func TestSecretReconciler(t *testing.T) {
  function loadKeypair (line 318) | func loadKeypair(t *testing.T, pub, pri string) (pubKey, priKey nacl.Key...

FILE: pkg/lockbox-server/serve.go
  function PublicKey (line 11) | func PublicKey(pubKey nacl.Key) http.Handler {

FILE: pkg/statemetrics/collector.go
  type Kubernetes (line 11) | type Kubernetes interface
  type kubernetes (line 20) | type kubernetes struct
    method Set (line 27) | func (k *kubernetes) Set(val float64) {
    method Describe (line 32) | func (k *kubernetes) Describe(ch chan<- *prometheus.Desc) {
    method Collect (line 37) | func (k *kubernetes) Collect(ch chan<- prometheus.Metric) {
  type KubernetesVec (line 45) | type KubernetesVec struct
    method WithLabelValues (line 74) | func (v *KubernetesVec) WithLabelValues(uid types.UID, lvs ...string) ...
    method Delete (line 89) | func (v *KubernetesVec) Delete(uid types.UID) {
    method Describe (line 97) | func (v *KubernetesVec) Describe(ch chan<- *prometheus.Desc) {
    method Collect (line 102) | func (v *KubernetesVec) Collect(ch chan<- prometheus.Metric) {
  type KubernetesOpts (line 52) | type KubernetesOpts
  function NewKubernetesVec (line 56) | func NewKubernetesVec(opts KubernetesOpts, labelNames []string) *Kuberne...
  type LabelsVec (line 115) | type LabelsVec struct
    method With (line 132) | func (v *LabelsVec) With(uid types.UID, l prometheus.Labels) Kubernetes {
    method Delete (line 162) | func (v *LabelsVec) Delete(uid types.UID) {
    method Describe (line 171) | func (v *LabelsVec) Describe(chan<- *prometheus.Desc) {}
    method Collect (line 174) | func (v *LabelsVec) Collect(ch chan<- prometheus.Metric) {
  function NewLabelsVec (line 122) | func NewLabelsVec(opts KubernetesOpts) *LabelsVec {

FILE: pkg/statemetrics/handler.go
  type StateMetricProxy (line 15) | type StateMetricProxy struct
    method Create (line 40) | func (s *StateMetricProxy) Create(ctx context.Context, evt event.Creat...
    method Update (line 49) | func (s *StateMetricProxy) Update(ctx context.Context, evt event.Updat...
    method Delete (line 58) | func (s *StateMetricProxy) Delete(ctx context.Context, evt event.Delet...
    method Generic (line 74) | func (s *StateMetricProxy) Generic(ctx context.Context, evt event.Gene...
    method updateWith (line 81) | func (s *StateMetricProxy) updateWith(obj client.Object) {
  function NewStateMetricProxy (line 27) | func NewStateMetricProxy(enqueuer handler.EventHandler, info, created, r...

FILE: pkg/statemetrics/handler_test.go
  function TestStateMetricsProxy_Create (line 16) | func TestStateMetricsProxy_Create(t *testing.T) {
  function TestStateMetricsProxy_Update (line 71) | func TestStateMetricsProxy_Update(t *testing.T) {
  function TestStateMetricsProxy_Delete (line 149) | func TestStateMetricsProxy_Delete(t *testing.T) {
  function createMetricVectors (line 190) | func createMetricVectors(t *testing.T) (info, created, resourceVersion, ...

FILE: pkg/statemetrics/labels.go
  function sanitizeLabel (line 12) | func sanitizeLabel(l string) string {
  function kubernetesLabelsToPrometheusLabels (line 18) | func kubernetesLabelsToPrometheusLabels(labels map[string]string) promet...

FILE: pkg/statemetrics/labels_test.go
  constant reservedLabelPrefix (line 11) | reservedLabelPrefix = "__"
  function TestLabelsTransformation (line 13) | func TestLabelsTransformation(t *testing.T) {
  function checkLabelName (line 31) | func checkLabelName(l string) bool {

FILE: pkg/util/conditions/conditions.go
  type Getter (line 28) | type Getter interface
  type Setter (line 32) | type Setter interface
  function FalseCondition (line 37) | func FalseCondition(t lockboxv1.ConditionType, reason string, severity l...
  function TrueCondition (line 47) | func TrueCondition(t lockboxv1.ConditionType) *lockboxv1.Condition {
  function UnknownCondition (line 54) | func UnknownCondition(t lockboxv1.ConditionType, reason string, message ...
  function Get (line 63) | func Get(from Getter, t lockboxv1.ConditionType) *lockboxv1.Condition {
  function Set (line 78) | func Set(to Setter, condition *lockboxv1.Condition) {
  function lexicographicLess (line 113) | func lexicographicLess(i, j *lockboxv1.Condition) bool {
  function hasSameState (line 117) | func hasSameState(i, j *lockboxv1.Condition) bool {
Condensed preview — 46 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (145K chars).
[
  {
    "path": ".dockerignore",
    "chars": 5,
    "preview": "bin/\n"
  },
  {
    "path": ".github/workflows/docker.yaml",
    "chars": 816,
    "preview": "name: Docker\non:\n  - pull_request\n  - push\njobs:\n  docker:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/c"
  },
  {
    "path": ".github/workflows/semgrep.yml",
    "chars": 571,
    "preview": "on:\n  pull_request: {}\n  workflow_dispatch: {}\n  push:\n    branches:\n      - trunk\n  schedule:\n    - cron: \"0 0 * * *\"\nn"
  },
  {
    "path": ".github/workflows/tests.yaml",
    "chars": 918,
    "preview": "name: Test\non:\n  - pull_request\n  - push\njobs:\n  unit:\n    runs-on: ubuntu-latest\n    name: \"Go ${{ matrix.go }} Test\"\n "
  },
  {
    "path": ".gitignore",
    "chars": 200,
    "preview": "## Go.gitignore ##\n\n# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, built with `go t"
  },
  {
    "path": "LICENSE",
    "chars": 1525,
    "preview": "BSD 3-Clause License\n\nCopyright (c) 2020, Cloudflare, Inc.\nAll rights reserved.\n\nRedistribution and use in source and bi"
  },
  {
    "path": "Makefile",
    "chars": 2560,
    "preview": ".DEFAULT_GOAL := binaries\n\nKERNEL := $(shell uname -s)\nGOTESTSUM := $(shell command -v gotestsum 2> /dev/null)\n\nDIB ?= d"
  },
  {
    "path": "README.org",
    "chars": 1206,
    "preview": "#+TITLE: Lockbox\n\n[[https://pkg.go.dev/github.com/cloudflare/lockbox][https://pkg.go.dev/badge/github.com/cloudflare/loc"
  },
  {
    "path": "cmd/lockbox-controller/Dockerfile",
    "chars": 462,
    "preview": "FROM docker.io/library/golang:1.21.5-bookworm AS builder\nWORKDIR /go/src/app\nADD . /go/src/app\n\nRUN --mount=type=cache,t"
  },
  {
    "path": "cmd/lockbox-controller/keypair.go",
    "chars": 910,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/kevinburke/nacl\"\n\t\"sigs.k8s.io/yaml\"\n)\n\ntype kp struct {\n\tPrivate []by"
  },
  {
    "path": "cmd/lockbox-controller/main.go",
    "chars": 6394,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"runtime\"\n\t\"time\"\n\n\tlockboxv1 \"github.com/clo"
  },
  {
    "path": "cmd/lockbox-keypair/main.go",
    "chars": 418,
    "preview": "package main\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/kevinburke/nacl/box\"\n)\n\nfunc main() "
  },
  {
    "path": "cmd/locket/main.go",
    "chars": 5532,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\tgruntime \"runtime\"\n\t\"time\"\n\n\tlockboxv1 \"git"
  },
  {
    "path": "deployment/crds/lockbox.k8s.cloudflare.com_lockboxes.yaml",
    "chars": 6717,
    "preview": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubeb"
  },
  {
    "path": "deployment/manifests/deployment-lockbox.yaml",
    "chars": 763,
    "preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: lockbox-controller\n  namespace: lockbox\nspec:\n  replicas: 1\n  sel"
  },
  {
    "path": "deployment/manifests/namespace-lockbox.yaml",
    "chars": 57,
    "preview": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: lockbox\n"
  },
  {
    "path": "deployment/manifests/service-lockbox.yaml",
    "chars": 180,
    "preview": "kind: Service\napiVersion: v1\nmetadata:\n  name: lockbox\n  namespace: lockbox\nspec:\n  ports:\n  - port: 80\n    targetPort: "
  },
  {
    "path": "deployment/manifests/serviceaccount-lockbox.yaml",
    "chars": 94,
    "preview": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: lockbox-controller\n  namespace: lockbox\n"
  },
  {
    "path": "deployment/rbac/proxier.yaml",
    "chars": 507,
    "preview": "apiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: lockbox-proxier\n  namespace: lockbox\nrules:\n  - ap"
  },
  {
    "path": "deployment/rbac/role-binding.yaml",
    "chars": 284,
    "preview": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: lockbox-controller\nroleRef:\n  apiGro"
  },
  {
    "path": "deployment/rbac/role.yaml",
    "chars": 517,
    "preview": "---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: lockbox-controller\nrules:\n- apiGroups:\n"
  },
  {
    "path": "go.mod",
    "chars": 3404,
    "preview": "module github.com/cloudflare/lockbox\n\ngo 1.21\n\ntoolchain go1.21.5\n\nrequire (\n\tgithub.com/go-logr/zerologr v1.2.3\n\tgithub"
  },
  {
    "path": "go.sum",
    "chars": 22213,
    "preview": "github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:"
  },
  {
    "path": "pkg/apis/lockbox.k8s.cloudflare.com/v1/groupversion_info.go",
    "chars": 829,
    "preview": "// +kubebuilder:object:generate=true\n// +groupName=lockbox.k8s.cloudflare.com\n\n// Package v1 is the v1 version of the Lo"
  },
  {
    "path": "pkg/apis/lockbox.k8s.cloudflare.com/v1/lockbox.go",
    "chars": 2449,
    "preview": "package v1\n\nimport (\n\t\"github.com/kevinburke/nacl\"\n\t\"github.com/kevinburke/nacl/box\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav"
  },
  {
    "path": "pkg/apis/lockbox.k8s.cloudflare.com/v1/lockbox_test.go",
    "chars": 6091,
    "preview": "package v1_test\n\nimport (\n\t\"crypto/rand\"\n\t\"testing\"\n\n\tv1 \"github.com/cloudflare/lockbox/pkg/apis/lockbox.k8s.cloudflare."
  },
  {
    "path": "pkg/apis/lockbox.k8s.cloudflare.com/v1/types.go",
    "chars": 4686,
    "preview": "package v1\n\nimport (\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// +kubebuilder:obje"
  },
  {
    "path": "pkg/apis/lockbox.k8s.cloudflare.com/v1/zz_generated.deepcopy.go",
    "chars": 5520,
    "preview": "//go:build !ignore_autogenerated\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v1\n\nimport (\n\truntime \"k8s."
  },
  {
    "path": "pkg/flagvar/enum.go",
    "chars": 508,
    "preview": "package flagvar\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n)\n\nvar ErrInvalidEnum = errors.New(\"invalid enum option\")\n\ntype En"
  },
  {
    "path": "pkg/flagvar/enum_test.go",
    "chars": 1458,
    "preview": "package flagvar_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/cloudflare/lockbox/pkg/flagvar\"\n\t\"gotest.tools/v3/assert\"\n)\n\nfun"
  },
  {
    "path": "pkg/flagvar/file.go",
    "chars": 771,
    "preview": "package flagvar\n\nimport (\n\t\"os\"\n)\n\n// File is a flag.Value for file paths. Returns any errors from os.Stat.\ntype File st"
  },
  {
    "path": "pkg/flagvar/file_test.go",
    "chars": 1446,
    "preview": "package flagvar_test\n\nimport (\n\t\"io/fs\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/cloudflare/lockbox/pkg/flagvar\"\n\t\"gote"
  },
  {
    "path": "pkg/flagvar/tcp_addr.go",
    "chars": 957,
    "preview": "package flagvar\n\nimport \"net\"\n\n// TCPAddr is a flag.Value for file paths. Returns any errors from net.ResolveTCPAddr.\nty"
  },
  {
    "path": "pkg/flagvar/tcp_addr_test.go",
    "chars": 1718,
    "preview": "package flagvar_test\n\nimport (\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/cloudflare/lockbox/pkg/flagvar\"\n\t\"gotest.tools/v3/assert\""
  },
  {
    "path": "pkg/flagvar/testdata/file",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "pkg/lockbox-controller/secretreconciler.go",
    "chars": 6574,
    "preview": "package controller\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\n\tlockboxv1 \"github.com/cloudflare/lockbox/pkg/apis/lo"
  },
  {
    "path": "pkg/lockbox-controller/secretreconciler_suite_test.go",
    "chars": 8516,
    "preview": "//go:build suite\n// +build suite\n\npackage controller_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"te"
  },
  {
    "path": "pkg/lockbox-controller/secretreconciler_test.go",
    "chars": 14146,
    "preview": "package controller_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\tlockboxv1 \"github.com/cloudflare/lockbox/pkg/apis/lockbox.k8s."
  },
  {
    "path": "pkg/lockbox-server/serve.go",
    "chars": 386,
    "preview": "package server\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/kevinburke/nacl\"\n)\n\n// PublicKey creates an HTTP handler that respons"
  },
  {
    "path": "pkg/statemetrics/collector.go",
    "chars": 4505,
    "preview": "package statemetrics\n\nimport (\n\t\"sync\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"k8s.io/apimachinery/pkg/type"
  },
  {
    "path": "pkg/statemetrics/handler.go",
    "chars": 3158,
    "preview": "package statemetrics\n\nimport (\n\t\"context\"\n\t\"encoding/hex\"\n\n\tlockboxv1 \"github.com/cloudflare/lockbox/pkg/apis/lockbox.k8"
  },
  {
    "path": "pkg/statemetrics/handler_test.go",
    "chars": 7268,
    "preview": "package statemetrics\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\tlockboxv1 \"github.com/cloudflare/lockbox/pkg/a"
  },
  {
    "path": "pkg/statemetrics/labels.go",
    "chars": 653,
    "preview": "package statemetrics\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\nvar invalidLabelCharRE = "
  },
  {
    "path": "pkg/statemetrics/labels_test.go",
    "chars": 584,
    "preview": "package statemetrics\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\t\"testing/quick\"\n\n\t\"github.com/prometheus/common/model\"\n)\n\nconst re"
  },
  {
    "path": "pkg/util/conditions/conditions.go",
    "chars": 3213,
    "preview": "// Adapted from https://github.com/kubernetes-sigs/cluster-api/tree/v0.3.10/util/conditions\n//\n// Copyright 2020 The Kub"
  },
  {
    "path": "tools/tools.go",
    "chars": 113,
    "preview": "//go:build tools\n// +build tools\n\npackage tools\n\nimport (\n\t_ \"sigs.k8s.io/controller-tools/cmd/controller-gen\"\n)\n"
  }
]

About this extraction

This page contains the full source code of the cloudflare/lockbox GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 46 files (128.7 KB), approximately 49.5k tokens, and a symbol index with 127 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!