[
  {
    "path": ".dockerignore",
    "content": "bin/\n"
  },
  {
    "path": ".github/workflows/docker.yaml",
    "content": "name: Docker\non:\n  - pull_request\n  - push\njobs:\n  docker:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: docker/setup-qemu-action@v3\n      - uses: docker/metadata-action@v5\n        id: docker-meta\n        with:\n          images: cloudflare/lockbox\n      - uses: docker/setup-buildx-action@v3\n      - uses: docker/login-action@v3\n        if: ${{ startsWith(github.ref, 'refs/tags/v') }}\n        with:\n          username: ${{ secrets.DOCKER_HUB_USERNAME }}\n          password: ${{ secrets.DOCKER_HUB_TOKEN }}\n      - uses: docker/build-push-action@v5\n        with:\n          file: ./cmd/lockbox-controller/Dockerfile\n          platforms: linux/amd64, linux/arm64\n          tags: ${{ steps.docker-meta.outputs.tags }}\n          push: ${{ startsWith(github.ref, 'refs/tags/v') }}\n"
  },
  {
    "path": ".github/workflows/semgrep.yml",
    "content": "on:\n  pull_request: {}\n  workflow_dispatch: {}\n  push:\n    branches:\n      - trunk\n  schedule:\n    - cron: \"0 0 * * *\"\nname: Semgrep config\njobs:\n  semgrep:\n    name: semgrep/ci\n    runs-on: ubuntu-latest\n    env:\n      SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}\n      SEMGREP_URL: https://cloudflare.semgrep.dev\n      SEMGREP_APP_URL: https://cloudflare.semgrep.dev\n      SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version\n    container:\n      image: semgrep/semgrep\n    steps:\n      - uses: actions/checkout@v4\n      - run: semgrep ci\n"
  },
  {
    "path": ".github/workflows/tests.yaml",
    "content": "name: Test\non:\n  - pull_request\n  - push\njobs:\n  unit:\n    runs-on: ubuntu-latest\n    name: \"Go ${{ matrix.go }} Test\"\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-go@v4\n        with:\n          go-version: \"stable\"\n      - run: make test\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-go@v4\n        with:\n          go-version: \"stable\"\n      - uses: dominikh/staticcheck-action@v1\n        with:\n          build-tags: suite\n          install-go: false\n  integration:\n    needs:\n      - unit\n      - lint\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-go@v4\n        with:\n          go-version: \"stable\"\n      - run: |\n          go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest\n          source <(setup-envtest use -p env)\n          go test ./... -tags suite\n"
  },
  {
    "path": ".gitignore",
    "content": "## 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 test -c`\n*.test\n\n# Vendor directory\n/vendor\n\n## nix.gitignore ##\n\n/result*\n/bin/\n"
  },
  {
    "path": "LICENSE",
    "content": "BSD 3-Clause License\n\nCopyright (c) 2020, Cloudflare, Inc.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its\n   contributors may be used to endorse or promote products derived from\n   this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n"
  },
  {
    "path": "Makefile",
    "content": ".DEFAULT_GOAL := binaries\n\nKERNEL := $(shell uname -s)\nGOTESTSUM := $(shell command -v gotestsum 2> /dev/null)\n\nDIB ?= docker\nIMAGE_ROOT ?= localhost/lockbox\nIMAGE_VERSION ?= $(shell git log -1 --pretty=format:%cd-%h --date short HEAD)\nVERSION := $(shell git describe --tags --always --dirty=-dev)\n# Build docker images for the native arch, but allow overriding in the environment for local development\nPLATFORM ?= local\n\n# Bind mount $SSL_CERT_FILE (or default) to build container if the file exists.\nSSL_CERT_FILE ?= /etc/ssl/certs/ca-certificates.crt\nifneq (,$(wildcard ${SSL_CERT_FILE}))\nSECRETS = --secret id=certificates,src=${SSL_CERT_FILE}\nendif\n\n# When compiling for Linux enable Security's recommend hardening to satisfy `checksec' checks.\n# Unfortunately, most of these flags aren't portable to other operating systems.\nifeq (${KERNEL},Linux)\n\tCGO_ENABLED ?= 1\n\tCPPFLAGS ?= -D_FORTIFY_SOURCE=2 -fstack-protector-all\n\tCFLAGS ?= -O2 -pipe -fno-plt\n\tCXXFLAGS ?= -O2 -pipe -fno-plt\n\tLDFLAGS ?= -Wl,-O1,-sort-common,-as-needed,-z,relro,-z,now\n\tGO_LDFLAGS ?= -linkmode=external\n\tGOFLAGS ?= -buildmode=pie\nendif\n\nGO_LDFLAGS += -w -s -X main.version=${VERSION}\nGOFLAGS += -v\n\nexport CGO_ENABLED\nexport CGO_CPPFLAGS ?= ${CPPFLAGS}\nexport CGO_CFLAGS ?= ${CFLAGS}\nexport CGO_CXXFLAGS ?= ${CXXFLAGS}\nexport CGO_LDFLAGS ?= ${LDFLAGS}\n\nCMDS := $(shell find cmd -mindepth 1 -maxdepth 1 -type d | awk -F '/' '{ print $$NF }' )\nIMAGES := $(shell find cmd -mindepth 1 -type f -name Dockerfile | awk -F '/' '{ print $$2 }')\n\ndefine make-go-target\n.PHONY: bin/$1\nbin/$1:\n\tgo build ${GOFLAGS} -o $$@ -ldflags \"${GO_LDFLAGS}\" ./cmd/$1\nendef\n\ndefine make-dib-targets\n.PHONY: images/$1\nimages/$1:\n\t${DIB} buildx build --platform \"$(PLATFORM)\" ${SECRETS} -f cmd/$1/Dockerfile -t \"${IMAGE_ROOT}/$1:${IMAGE_VERSION}\" .\n\n.PHONY: push/images/$1\npush/images/$1:\n\t${DIB} push \"${IMAGE_ROOT}/$1:${IMAGE_VERSION}\"\nendef\n\n$(foreach element,$(CMDS), $(eval $(call make-go-target,$(element))))\n$(foreach element,$(IMAGES), $(eval $(call make-dib-targets,$(element))))\n\n.PHONY: binaries\nbinaries: $(CMDS:%=bin/%)\n\n.PHONY: images\nimages: $(IMAGES:%=images/%)\n\n.PHONY: push-images\npush-images: $(IMAGES:%=push/images/%)\n\n.PHONY: clean\nclean:\n\trm -rf bin\n\n.PHONY: test\ntest:\nifdef GOTESTSUM\n\t\"${GOTESTSUM}\" -- -count 1 ./...\nelse\n\tgo test -cover -count 1 ./...\nendif\n\n.PHONY: lint\nlint:\n\tstaticcheck -tags suite ./...\n\n.PHONY: controller-gen\ncontroller-gen:\n\tgo install sigs.k8s.io/controller-tools/cmd/controller-gen\n\n.PHONY: go-generate\ngo-generate: controller-gen\n\tgo generate -v ./...\n"
  },
  {
    "path": "README.org",
    "content": "#+TITLE: Lockbox\n\n[[https://pkg.go.dev/github.com/cloudflare/lockbox][https://pkg.go.dev/badge/github.com/cloudflare/lockbox.png]]\n\nLockbox 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.\n\n** Features\n+ Secure encryption using modern cryptography. Uses Salsa20, Poly1305, and Curve25519.\n+ Secrets are locked to specific namespaces.\n+ All Kubernetes Secret types are supported.\n+ Plays nicely with Secrets created by other controllers.\n+ Continuously reconciles child resources.\n\n** Example Usage\nCreate a native Secret, but pass =--dry-run= to avoid submitting to the API.\n\n#+begin_example\n$ kubectl create secret generic mysecret --namespace default \\\n  --from-literal=foo=bar --dry-run -o yaml > mysecret.yaml\n#+end_example\n\nThen, use locket to encrypt the secret.\n\n#+begin_example\n$ locket -f mysecret.yaml > mylockbox.yaml\n#+end_example\n\nSubmit the lockbox to the API.\n\n#+begin_example\n$ kubectl create -f mylockbox.yaml\n#+end_example\n\nRemove the unencrypted secret.\n\n#+begin_example\n$ rm mysecret.yaml\n#+end_example\n"
  },
  {
    "path": "cmd/lockbox-controller/Dockerfile",
    "content": "FROM docker.io/library/golang:1.21.5-bookworm AS builder\nWORKDIR /go/src/app\nADD . /go/src/app\n\nRUN --mount=type=cache,target=/go/pkg/mod \\\n    --mount=type=cache,target=/root/.cache/go-build \\\n    --mount=type=secret,id=certificates,target=/etc/ssl/certs/ca-certificates.crt \\\n    make bin/lockbox-controller\n\n\nFROM gcr.io/distroless/base-nossl-debian12:nonroot\nCOPY --from=builder /go/src/app/bin/lockbox-controller /bin\nENTRYPOINT [\"/bin/lockbox-controller\"]\n"
  },
  {
    "path": "cmd/lockbox-controller/keypair.go",
    "content": "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 []byte `json:\"private\"`\n\tPublic  []byte `json:\"public\"`\n}\n\n// KeyPairFromYAMLOrJSON loads a public/private NaCL keypair from a YAML or JSON file.\nfunc KeyPairFromYAMLOrJSON(r io.Reader) (pub, pri nacl.Key, err error) {\n\tdata, err := io.ReadAll(r)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tkeypair := kp{}\n\terr = yaml.Unmarshal(data, &keypair, yaml.DisallowUnknownFields)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif len(keypair.Private) != 32 {\n\t\terr = fmt.Errorf(\"incorrect private key length: %d, should be 32\", len(keypair.Private))\n\t\treturn\n\t}\n\tif len(keypair.Public) != 32 {\n\t\terr = fmt.Errorf(\"incorrect public key length: %d, should be 32\", len(keypair.Public))\n\t\treturn\n\t}\n\n\tpub = new([nacl.KeySize]byte)\n\tpri = new([nacl.KeySize]byte)\n\n\tcopy(pri[:], keypair.Private)\n\tcopy(pub[:], keypair.Public)\n\treturn\n}\n"
  },
  {
    "path": "cmd/lockbox-controller/main.go",
    "content": "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/cloudflare/lockbox/pkg/apis/lockbox.k8s.cloudflare.com/v1\"\n\t\"github.com/cloudflare/lockbox/pkg/flagvar\"\n\tlockboxcontroller \"github.com/cloudflare/lockbox/pkg/lockbox-controller\"\n\tserver \"github.com/cloudflare/lockbox/pkg/lockbox-server\"\n\t\"github.com/cloudflare/lockbox/pkg/statemetrics\"\n\t\"github.com/go-logr/zerologr\"\n\t\"github.com/kevinburke/nacl\"\n\t\"github.com/rs/zerolog\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"sigs.k8s.io/controller-runtime/pkg/cache\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client/config\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller\"\n\t\"sigs.k8s.io/controller-runtime/pkg/handler\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/manager\"\n\t\"sigs.k8s.io/controller-runtime/pkg/manager/signals\"\n\t\"sigs.k8s.io/controller-runtime/pkg/metrics\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\t\"sigs.k8s.io/controller-runtime/pkg/source\"\n)\n\nvar (\n\tpubKey, priKey nacl.Key\n\tversion        = \"dev\"\n\tsyncPeriod     = 1 * time.Hour\n\tkeypairPath    = flagvar.File{Value: \"/etc/lockbox/keypair.yaml\"}\n\tmetricsAddr    = flagvar.TCPAddr{Text: \":8080\"}\n\thttpAddr       = flagvar.TCPAddr{Text: \":8081\"}\n)\n\nfunc main() {\n\tflag.Var(&keypairPath, \"keypair\", fmt.Sprintf(\"public/private 32 byte keypairs (%s)\", keypairPath.Help()))\n\tflag.Var(&metricsAddr, \"metrics-addr\", fmt.Sprintf(\"bind for HTTP metrics (%s)\", metricsAddr.Help()))\n\tflag.Var(&httpAddr, \"http-addr\", fmt.Sprintf(\"bind for HTTP server (%s)\", httpAddr.Help()))\n\tflag.DurationVar(&syncPeriod, \"sync-period\", syncPeriod, \"controller sync period\")\n\tflag.String(\"v\", \"\", \"log level for V logs\")\n\tflag.Parse()\n\n\tzerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs\n\tzerologr.NameFieldName = \"logger\"\n\tzerologr.NameSeparator = \"/\"\n\n\tzl := zerolog.New(os.Stderr).With().Caller().Timestamp().Logger()\n\tlogf.SetLogger(zerologr.New(&zl))\n\tlogger := zl.With().Str(\"name\", \"main\").Logger()\n\n\tkeypair, err := os.Open(keypairPath.Value)\n\tif err != nil {\n\t\tlogger.Fatal().Err(err).Str(\"path\", keypairPath.Value).Msg(\"unable to open keypair\")\n\t\tos.Exit(1)\n\t}\n\tpubKey, priKey, err = KeyPairFromYAMLOrJSON(keypair)\n\tif err != nil {\n\t\tlogger.Fatal().Err(err).Str(\"path\", keypairPath.Value).Msg(\"unable to parse keypair\")\n\t\tos.Exit(1)\n\t}\n\tkeypair.Close()\n\n\terr = lockboxv1.AddToScheme(scheme.Scheme)\n\tif err != nil {\n\t\tlogger.Fatal().Err(err).Msg(\"unable to add lockbox schemes\")\n\t\tos.Exit(1)\n\t}\n\n\tcfg, err := config.GetConfig()\n\tcfg.UserAgent = fmt.Sprintf(\"%s/%s (%s/%s)\", os.Args[0], version, runtime.GOOS, runtime.GOARCH)\n\n\tif err != nil {\n\t\tlogger.Fatal().Err(err).Msg(\"unable to get kubeconfig\")\n\t\tos.Exit(1)\n\t}\n\n\tmgr, err := manager.New(cfg, manager.Options{\n\t\tMetrics: metricsserver.Options{\n\t\t\tBindAddress: metricsAddr.Text,\n\t\t},\n\t\tCache: cache.Options{\n\t\t\tSyncPeriod: &syncPeriod,\n\t\t},\n\t\tScheme: scheme.Scheme,\n\t})\n\tif err != nil {\n\t\tlogger.Fatal().Err(err).Msg(\"unable to create controller manager\")\n\t\tos.Exit(1)\n\t}\n\n\trecorder := mgr.GetEventRecorderFor(\"lockbox\")\n\tclient := mgr.GetClient()\n\n\tsr := lockboxcontroller.NewSecretReconciler(pubKey, priKey, lockboxcontroller.WithRecorder(recorder), lockboxcontroller.WithClient(client))\n\n\tinfo := statemetrics.NewKubernetesVec(statemetrics.KubernetesOpts{\n\t\tName: \"kube_lockbox_info\",\n\t\tHelp: \"Information about Lockbox\",\n\t}, []string{\"namespace\", \"lockbox\"})\n\tcreated := statemetrics.NewKubernetesVec(statemetrics.KubernetesOpts{\n\t\tName: \"kube_lockbox_created\",\n\t\tHelp: \"Unix creation timestamp\",\n\t}, []string{\"namespace\", \"lockbox\"})\n\tresourceVersion := statemetrics.NewKubernetesVec(statemetrics.KubernetesOpts{\n\t\tName: \"kube_lockbox_resource_version\",\n\t\tHelp: \"Resource version representing a specific version of a Lockbox\",\n\t}, []string{\"namespace\", \"lockbox\", \"resource_version\"})\n\tlbType := statemetrics.NewKubernetesVec(statemetrics.KubernetesOpts{\n\t\tName: \"kube_lockbox_type\",\n\t\tHelp: \"Lockbox secret type\",\n\t}, []string{\"namespace\", \"lockbox\", \"type\"})\n\tpeerKey := statemetrics.NewKubernetesVec(statemetrics.KubernetesOpts{\n\t\tName: \"kube_lockbox_peer\",\n\t\tHelp: \"Lockbox peer key\",\n\t}, []string{\"namespace\", \"lockbox\", \"peer\"})\n\tlabels := statemetrics.NewLabelsVec(statemetrics.KubernetesOpts{\n\t\tName: \"kube_lockbox_labels\",\n\t\tHelp: \"Kubernetes labels converted to Prometheus labels\",\n\t})\n\tmetrics.Registry.MustRegister(info, created, resourceVersion, lbType, labels, peerKey)\n\n\tmh := statemetrics.NewStateMetricProxy(\n\t\t&handler.EnqueueRequestForObject{},\n\t\tinfo, created, resourceVersion,\n\t\tlbType, peerKey, labels,\n\t)\n\n\tc, err := controller.New(\"lockbox-controller\", mgr, controller.Options{\n\t\tReconciler: reconcile.AsReconciler(mgr.GetClient(), sr),\n\t})\n\n\tif err != nil {\n\t\tlogger.Fatal().Err(err).Msg(\"unable to create controller\")\n\t\tos.Exit(1)\n\t}\n\n\tif err := c.Watch(source.Kind(mgr.GetCache(), &lockboxv1.Lockbox{}), mh); err != nil {\n\t\tlogger.Fatal().Err(err).Msg(\"unable to watch Lockbox resources\")\n\t\tos.Exit(1)\n\t}\n\n\tif err := c.Watch(source.Kind(mgr.GetCache(), &corev1.Secret{}), handler.EnqueueRequestForOwner(scheme.Scheme, mgr.GetRESTMapper(), &lockboxv1.Lockbox{}, handler.OnlyControllerOwner())); err != nil {\n\t\tlogger.Fatal().Err(err).Msg(\"unable to watch Secret resources\")\n\t\tos.Exit(1)\n\t}\n\n\t// TODO(terin): make server implement Runnable\n\tif err := mgr.Add(manager.RunnableFunc(func(ctx context.Context) error {\n\t\tmux := http.NewServeMux()\n\t\tmux.Handle(\"/v1/public\", server.PublicKey(pubKey))\n\n\t\tln, err := net.Listen(\"tcp\", httpAddr.Text)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// sig.kubernetes.io/controller-runtime/pkg/internal/httpserver\n\t\ts := http.Server{\n\t\t\tHandler:           mux,\n\t\t\tMaxHeaderBytes:    1 << 20,\n\t\t\tIdleTimeout:       90 * time.Second,\n\t\t\tReadHeaderTimeout: 32 * time.Second,\n\t\t}\n\n\t\tidleConnsClosed := make(chan struct{})\n\t\tgo func() {\n\t\t\t<-ctx.Done()\n\n\t\t\tif err := s.Shutdown(context.Background()); err != nil {\n\t\t\t\tlogger.Err(err).Send()\n\t\t\t}\n\t\t\tclose(idleConnsClosed)\n\t\t}()\n\n\t\tif err := s.Serve(ln); err != nil && err != http.ErrServerClosed {\n\t\t\treturn err\n\t\t}\n\n\t\t<-idleConnsClosed\n\t\treturn nil\n\t})); err != nil {\n\t\tlogger.Fatal().Err(err).Msg(\"unable to add server runnable\")\n\t}\n\n\tif err := mgr.Start(signals.SetupSignalHandler()); err != nil {\n\t\tlogger.Fatal().Err(err).Send()\n\t}\n}\n"
  },
  {
    "path": "cmd/lockbox-keypair/main.go",
    "content": "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() {\n\tlockboxPubKey, lockboxPriKey, err := box.GenerateKey(rand.Reader)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tpub64 := base64.StdEncoding.EncodeToString(lockboxPubKey[:])\n\tpri64 := base64.StdEncoding.EncodeToString(lockboxPriKey[:])\n\n\tfmt.Fprintf(os.Stdout, \"public:  %s\\nprivate: %s\\n\", pub64, pri64)\n}\n"
  },
  {
    "path": "cmd/locket/main.go",
    "content": "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 \"github.com/cloudflare/lockbox/pkg/apis/lockbox.k8s.cloudflare.com/v1\"\n\t\"github.com/cloudflare/lockbox/pkg/flagvar\"\n\t\"github.com/go-logr/zerologr\"\n\t\"github.com/kevinburke/nacl\"\n\t\"github.com/kevinburke/nacl/box\"\n\t\"github.com/rs/zerolog\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\truntimeserializer \"k8s.io/apimachinery/pkg/runtime/serializer\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\tclientcmdapi \"k8s.io/client-go/tools/clientcmd/api\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n)\n\nvar (\n\tinput        = flagvar.File{}\n\tkubeconfig   = flagvar.File{}\n\toutput       = flagvar.Enum{Choices: []string{\"json\", \"yaml\"}, Value: \"yaml\"}\n\tversion      = \"dev\"\n\tprintVersion bool\n\tpeerHex      string\n\tmasterURL    string\n\tlockboxNS    string\n\tlockboxSvc   string\n)\n\nfunc main() {\n\tflag.Var(&input, \"f\", fmt.Sprintf(\"input file (%s)\", input.Help()))\n\tflag.Var(&output, \"o\", fmt.Sprintf(\"output format (%s)\", output.Help()))\n\tflag.Var(&kubeconfig, \"kubeconfig\", fmt.Sprintf(\"path to kubeconfig. (%s)\", kubeconfig.Help()))\n\tflag.StringVar(&peerHex, \"peer-hex\", \"\", \"peer public key (32-bit hex)\")\n\tflag.StringVar(&masterURL, \"master\", \"\", \"The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.\")\n\tflag.StringVar(&lockboxNS, \"lockbox-namespace\", \"lockbox\", \"namespace of the lockbox controller\")\n\tflag.StringVar(&lockboxSvc, \"lockbox-service\", \"lockbox\", \"name of the lockbox service\")\n\tflag.BoolVar(&printVersion, \"version\", false, \"print version\")\n\tflag.String(\"v\", \"\", \"log level for V logs\")\n\tflag.Parse()\n\n\tctx := context.Background()\n\n\tif printVersion {\n\t\tfmt.Printf(\"locket: %s\\n\", version)\n\t\tos.Exit(0)\n\t}\n\n\tzerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs\n\tzerologr.NameFieldName = \"logger\"\n\tzerologr.NameSeparator = \"/\"\n\n\tzl := zerolog.New(os.Stderr).With().Caller().Timestamp().Logger()\n\tlogf.SetLogger(zerologr.New(&zl))\n\tlogger := zl.With().Str(\"name\", \"main\").Logger()\n\n\terr := lockboxv1.AddToScheme(scheme.Scheme)\n\tif err != nil {\n\t\tlogger.Fatal().Err(err).Msg(\"unable to add lockbox schemes\")\n\t\tos.Exit(1)\n\t}\n\n\tvar r io.Reader\n\tif input.String() == \"\" {\n\t\tr = os.Stdin\n\t} else {\n\t\tr, err = os.Open(input.String())\n\t\tif err != nil {\n\t\t\tlogger.Fatal().Err(err).Msg(\"unable to open secret file\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\n\tw := os.Stdout\n\n\tcfg := GetConfig()\n\n\tcf := runtimeserializer.NewCodecFactory(scheme.Scheme)\n\n\tib, err := io.ReadAll(r)\n\tif err != nil {\n\t\tlogger.Fatal().Err(err).Msg(\"unable to read secret file\")\n\t\tos.Exit(1)\n\t}\n\tvar secret corev1.Secret\n\tif err = runtime.DecodeInto(cf.UniversalDecoder(), ib, &secret); err != nil {\n\t\tlogger.Fatal().Err(err).Msg(\"unable to decode secret file\")\n\t\tos.Exit(1)\n\t}\n\n\tpubKey, priKey, err := box.GenerateKey(rand.Reader)\n\tif err != nil {\n\t\tlogger.Fatal().Err(err).Msg(\"could not generate key\")\n\t\tos.Exit(1)\n\t}\n\n\tvar peerKey nacl.Key\n\tswitch peerHex {\n\tcase \"\":\n\t\tctx, cancel := context.WithTimeout(ctx, 30*time.Second)\n\t\tdefer cancel()\n\n\t\tcc, err := cfg.ClientConfig()\n\t\tif err != nil {\n\t\t\tlogger.Fatal().Err(err).Msg(\"unable to create API client configuration\")\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tcc.UserAgent = fmt.Sprintf(\"%s/%s (%s/%s)\", os.Args[0], version, gruntime.GOOS, gruntime.GOARCH)\n\n\t\tclient, err := kubernetes.NewForConfig(cc)\n\t\tif err != nil {\n\t\t\tlogger.Fatal().Err(err).Msg(\"unable to create API client\")\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tb, err := GetRemotePublicKey(ctx, client, lockboxNS, lockboxSvc)\n\t\tif err != nil {\n\t\t\tlogger.Fatal().Err(err).Msg(\"unable to fetch public key\")\n\t\t\tos.Exit(1)\n\t\t}\n\t\tif len(b) != 32 {\n\t\t\terr = fmt.Errorf(\"incorrect peer key length: %d, should be 32\", len(b))\n\t\t\tlogger.Fatal().Err(err).Msg(\"unable to fetch peer key\")\n\t\t\treturn\n\t\t}\n\n\t\tpeerKey = new([nacl.KeySize]byte)\n\t\tcopy(peerKey[:], b)\n\tdefault:\n\t\tpeerKey, err = nacl.Load(peerHex)\n\t\tif err != nil {\n\t\t\tlogger.Fatal().Err(err).Msg(\"could not load --peer-hex\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\n\tnamespace := secret.Namespace\n\tif namespace == \"\" {\n\t\tnamespace, _, _ = cfg.Namespace()\n\t}\n\n\tb := lockboxv1.NewFromSecret(secret, namespace, peerKey, pubKey, priKey)\n\n\tvar ct string\n\tswitch output.String() {\n\tcase \"yaml\":\n\t\tct = \"application/yaml\"\n\tcase \"json\":\n\t\tct = \"application/json\"\n\t}\n\n\tinfo, ok := runtime.SerializerInfoForMediaType(cf.SupportedMediaTypes(), ct)\n\tif !ok {\n\t\tlogger.Fatal().Str(\"content-type\", ct).Msg(\"can't serialize to content-type\")\n\t\tos.Exit(1)\n\t}\n\tserial := info.Serializer\n\tif info.PrettySerializer != nil {\n\t\tserial = info.PrettySerializer\n\t}\n\tenc := cf.EncoderForVersion(serial, lockboxv1.GroupVersion)\n\n\tob, err := runtime.Encode(enc, b)\n\tif err != nil {\n\t\tlogger.Fatal().Err(err).Msg(\"unable to encode Lockbox\")\n\t\tos.Exit(1)\n\t}\n\n\tif _, err := w.Write(ob); err != nil {\n\t\tlogger.Fatal().Err(err).Send()\n\t}\n\tif _, err := w.WriteString(\"\\n\"); err != nil {\n\t\tlogger.Fatal().Err(err).Send()\n\t}\n}\n\nfunc GetConfig() clientcmd.ClientConfig {\n\tloader := clientcmd.NewDefaultClientConfigLoadingRules()\n\toverrides := clientcmd.ConfigOverrides{\n\t\tClusterInfo: clientcmdapi.Cluster{\n\t\t\tServer: masterURL,\n\t\t},\n\t}\n\tloader.ExplicitPath = kubeconfig.String()\n\treturn clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loader, &overrides)\n}\n\nfunc GetRemotePublicKey(ctx context.Context, c kubernetes.Interface, ns, svc string) ([]byte, error) {\n\treturn c.CoreV1().Services(ns).ProxyGet(\"http\", svc, \"\", \"/v1/public\", nil).DoRaw(ctx)\n}\n"
  },
  {
    "path": "deployment/crds/lockbox.k8s.cloudflare.com_lockboxes.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.13.0\n  name: lockboxes.lockbox.k8s.cloudflare.com\nspec:\n  group: lockbox.k8s.cloudflare.com\n  names:\n    kind: Lockbox\n    listKind: LockboxList\n    plural: lockboxes\n    singular: lockbox\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .spec.template.type\n      name: SecretType\n      type: string\n    - jsonPath: .spec.peer\n      name: Peer\n      type: string\n    name: v1\n    schema:\n      openAPIV3Schema:\n        description: Lockbox is a struct wrapping the LockboxSpec in standard API\n          server metadata fields.\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: Desired state of the Lockbox resource.\n            properties:\n              data:\n                additionalProperties:\n                  format: byte\n                  type: string\n                description: Data contains the secret data, encrypted to the Peer's\n                  public key. Each key in the data map must consist of alphanumeric\n                  characters, '-', '_', or '.'.\n                type: object\n              namespace:\n                description: Namespace stores an encrypted copy of which namespace\n                  this Lockbox is locked for, ensuring it cannot be deployed to another\n                  namespace under an attacker's control.\n                format: byte\n                type: string\n              peer:\n                description: Peer stores the public key that can unlock this Lockbox.\n                format: byte\n                type: string\n              sender:\n                description: Sender stores the public key used to lock this Lockbox.\n                format: byte\n                type: string\n              template:\n                description: Template defines the structure of the Secret that will\n                  be created from this Lockbox.\n                properties:\n                  metadata:\n                    properties:\n                      annotations:\n                        additionalProperties:\n                          type: string\n                        description: 'Annotations is an unstructured key value map\n                          stored with a resource that may be set by external tools\n                          to store and retrieve arbitrary metadata. They are not queryable\n                          and should be preserved when modifying objects. More info:\n                          http://kubernetes.io/docs/user-guide/annotations'\n                        type: object\n                      labels:\n                        additionalProperties:\n                          type: string\n                        description: 'Map of string keys and values that can be used\n                          to organize and categorize (scope and select) objects. May\n                          match selectors of replication controllers and services.\n                          More info: http://kubernetes.io/docs/user-guide/labels'\n                        type: object\n                    type: object\n                  type:\n                    description: Type is used to facilitate programmatic handling\n                      of secret data.\n                    type: string\n                type: object\n            required:\n            - data\n            - namespace\n            - peer\n            - sender\n            type: object\n          status:\n            description: Status of the Lockbox. This is set and managed automatically.\n            properties:\n              conditions:\n                description: List of status conditions to indicate the status of a\n                  Lockbox.\n                items:\n                  description: Condition contains condition information for a Lockbox.\n                  properties:\n                    lastTransitionTime:\n                      description: LastTransitionTime marks when the condition last\n                        transitioned from one status to another. This should be when\n                        the underlying condition changed. If that is not known, then\n                        using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: A message is the human readable message indicating\n                        details about the transition. The field may be empty.\n                      type: string\n                    reason:\n                      description: The reason for the condition's last transition\n                        in CamelCase.\n                      type: string\n                    severity:\n                      description: Severity provides explicit classification of Reason\n                        code, so that users or machines can immediately understand\n                        the current situation and act accordingly. The Severity field\n                        MUST be set only when Status=False.\n                      enum:\n                      - Error\n                      - Warning\n                      - Info\n                      type: string\n                    status:\n                      description: Status of the condition, one of True, False, Unknown\n                      type: string\n                    type:\n                      description: Type of condition in CamelCase.\n                      enum:\n                      - Ready\n                      type: string\n                  required:\n                  - status\n                  - type\n                  type: object\n                type: array\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "deployment/manifests/deployment-lockbox.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: lockbox-controller\n  namespace: lockbox\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: lockbox\n      component: controller\n  template:\n    metadata:\n      labels:\n        app: lockbox\n        component: controller\n    spec:\n      serviceAccountName: lockbox-controller\n      containers:\n      - name: lockbox\n        image: cloudflare/lockbox:v0.6.0\n        ports:\n        - containerPort: 8080\n          name: http-metrics\n        - containerPort: 8081\n          name: http-api\n        volumeMounts:\n        - name: keypair\n          mountPath: /etc/lockbox/\n          readOnly: true\n      volumes:\n      - name: keypair\n        secret:\n          secretName: keypair\n          defaultMode: 256\n"
  },
  {
    "path": "deployment/manifests/namespace-lockbox.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: lockbox\n"
  },
  {
    "path": "deployment/manifests/service-lockbox.yaml",
    "content": "kind: Service\napiVersion: v1\nmetadata:\n  name: lockbox\n  namespace: lockbox\nspec:\n  ports:\n  - port: 80\n    targetPort: 8081\n  selector:\n    app: lockbox\n    component: controller\n"
  },
  {
    "path": "deployment/manifests/serviceaccount-lockbox.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: lockbox-controller\n  namespace: lockbox\n"
  },
  {
    "path": "deployment/rbac/proxier.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: lockbox-proxier\n  namespace: lockbox\nrules:\n  - apiGroups:\n      - \"\"\n    resources:\n      - \"services/proxy\"\n    resourceNames:\n      - \"http:lockbox:\"\n    verbs:\n      - \"get\"\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: lockbox-proxier\n  namespace: lockbox\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: lockbox-proxier\nsubjects:\n  - kind: Group\n    name: system:authenticated\n"
  },
  {
    "path": "deployment/rbac/role-binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: lockbox-controller\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: lockbox-controller\nsubjects:\n  - kind: ServiceAccount\n    name: lockbox-controller\n    namespace: lockbox\n"
  },
  {
    "path": "deployment/rbac/role.yaml",
    "content": "---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: lockbox-controller\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n- apiGroups:\n  - \"\"\n  resources:\n  - secrets\n  verbs:\n  - create\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - lockbox.k8s.cloudflare.com\n  resources:\n  - lockboxes\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - lockbox.k8s.cloudflare.com\n  resources:\n  - lockboxes/status\n  verbs:\n  - get\n  - patch\n  - update\n"
  },
  {
    "path": "go.mod",
    "content": "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.com/google/go-cmp v0.6.0\n\tgithub.com/kevinburke/nacl v0.0.0-20210405173606-cd9060f5f776\n\tgithub.com/prometheus/client_golang v1.18.0\n\tgithub.com/prometheus/common v0.45.0\n\tgithub.com/rs/zerolog v1.29.1\n\tgotest.tools/v3 v3.4.0\n\tk8s.io/api v0.29.0\n\tk8s.io/apimachinery v0.29.0\n\tk8s.io/client-go v0.29.0\n\tk8s.io/utils v0.0.0-20230726121419-3b25d923346b\n\tsigs.k8s.io/controller-runtime v0.17.0\n\tsigs.k8s.io/controller-tools v0.13.0\n\tsigs.k8s.io/yaml v1.4.0\n)\n\nrequire (\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.2.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/emicklei/go-restful/v3 v3.11.0 // indirect\n\tgithub.com/evanphx/json-patch v5.6.0+incompatible // indirect\n\tgithub.com/evanphx/json-patch/v5 v5.8.0 // indirect\n\tgithub.com/fatih/color v1.15.0 // indirect\n\tgithub.com/fsnotify/fsnotify v1.7.0 // indirect\n\tgithub.com/go-logr/logr v1.4.1 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.19.6 // indirect\n\tgithub.com/go-openapi/jsonreference v0.20.2 // indirect\n\tgithub.com/go-openapi/swag v0.22.3 // indirect\n\tgithub.com/gobuffalo/flect v1.0.2 // indirect\n\tgithub.com/gogo/protobuf v1.3.2 // indirect\n\tgithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect\n\tgithub.com/golang/protobuf v1.5.3 // indirect\n\tgithub.com/google/gnostic-models v0.6.8 // indirect\n\tgithub.com/google/gofuzz v1.2.0 // indirect\n\tgithub.com/google/uuid v1.3.0 // indirect\n\tgithub.com/imdario/mergo v0.3.12 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/mailru/easyjson v0.7.7 // indirect\n\tgithub.com/mattn/go-colorable v0.1.13 // indirect\n\tgithub.com/mattn/go-isatty v0.0.17 // indirect\n\tgithub.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.2 // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/prometheus/client_model v0.5.0 // indirect\n\tgithub.com/prometheus/procfs v0.12.0 // indirect\n\tgithub.com/spf13/cobra v1.7.0 // indirect\n\tgithub.com/spf13/pflag v1.0.5 // indirect\n\tgolang.org/x/crypto v0.16.0 // indirect\n\tgolang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect\n\tgolang.org/x/mod v0.14.0 // indirect\n\tgolang.org/x/net v0.19.0 // indirect\n\tgolang.org/x/oauth2 v0.12.0 // indirect\n\tgolang.org/x/sys v0.16.0 // indirect\n\tgolang.org/x/term v0.15.0 // indirect\n\tgolang.org/x/text v0.14.0 // indirect\n\tgolang.org/x/time v0.3.0 // indirect\n\tgolang.org/x/tools v0.16.1 // indirect\n\tgomodules.xyz/jsonpatch/v2 v2.4.0 // indirect\n\tgoogle.golang.org/appengine v1.6.7 // indirect\n\tgoogle.golang.org/protobuf v1.31.0 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tk8s.io/apiextensions-apiserver v0.29.0 // indirect\n\tk8s.io/component-base v0.29.0 // indirect\n\tk8s.io/klog/v2 v2.110.1 // indirect\n\tk8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect\n\tsigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect\n\tsigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=\ngithub.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=\ngithub.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=\ngithub.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=\ngithub.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=\ngithub.com/evanphx/json-patch/v5 v5.8.0 h1:lRj6N9Nci7MvzrXuX6HFzU8XjmhPiXPlsKEy1u0KQro=\ngithub.com/evanphx/json-patch/v5 v5.8.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=\ngithub.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=\ngithub.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=\ngithub.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=\ngithub.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=\ngithub.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=\ngithub.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=\ngithub.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=\ngithub.com/go-logr/zerologr v1.2.3 h1:up5N9vcH9Xck3jJkXzgyOxozT14R47IyDODz8LM1KSs=\ngithub.com/go-logr/zerologr v1.2.3/go.mod h1:BxwGo7y5zgSHYR1BjbnHPyF/5ZjVKfKxAZANVu6E8Ho=\ngithub.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=\ngithub.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=\ngithub.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=\ngithub.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=\ngithub.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=\ngithub.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=\ngithub.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=\ngithub.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=\ngithub.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA=\ngithub.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs=\ngithub.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=\ngithub.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=\ngithub.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=\ngithub.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=\ngithub.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=\ngithub.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=\ngithub.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=\ngithub.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=\ngithub.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/kevinburke/nacl v0.0.0-20210405173606-cd9060f5f776 h1:W8T7zJRO9imecUZySwPkuXHosjp2MloqAY1eSAEEOIo=\ngithub.com/kevinburke/nacl v0.0.0-20210405173606-cd9060f5f776/go.mod h1:VUp2yfq+wAk8hMl3NNN34fXjzUD9xMpGvUL8eSJz9Ns=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=\ngithub.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=\ngithub.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=\ngithub.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=\ngithub.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=\ngithub.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=\ngithub.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=\ngithub.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=\ngithub.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY=\ngithub.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw=\ngithub.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=\ngithub.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=\ngithub.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=\ngithub.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=\ngithub.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=\ngithub.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=\ngithub.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=\ngithub.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=\ngithub.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=\ngithub.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=\ngithub.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=\ngithub.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=\ngithub.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=\ngithub.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=\ngithub.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=\ngo.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=\ngolang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=\ngolang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=\ngolang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=\ngolang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=\ngolang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=\ngolang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=\ngolang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=\ngolang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=\ngolang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=\ngolang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=\ngolang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=\ngolang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=\ngolang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=\ngolang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=\ngomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=\ngoogle.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=\ngoogle.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=\ngoogle.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=\ngotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=\nk8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A=\nk8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA=\nk8s.io/apiextensions-apiserver v0.29.0 h1:0VuspFG7Hj+SxyF/Z/2T0uFbI5gb5LRgEyUVE3Q4lV0=\nk8s.io/apiextensions-apiserver v0.29.0/go.mod h1:TKmpy3bTS0mr9pylH0nOt/QzQRrW7/h7yLdRForMZwc=\nk8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o=\nk8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis=\nk8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8=\nk8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38=\nk8s.io/component-base v0.29.0 h1:T7rjd5wvLnPBV1vC4zWd/iWRbV8Mdxs+nGaoaFzGw3s=\nk8s.io/component-base v0.29.0/go.mod h1:sADonFTQ9Zc9yFLghpDpmNXEdHyQmFIGbiuZbqAXQ1M=\nk8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0=\nk8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo=\nk8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780=\nk8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=\nk8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=\nk8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=\nsigs.k8s.io/controller-runtime v0.17.0 h1:fjJQf8Ukya+VjogLO6/bNX9HE6Y2xpsO5+fyS26ur/s=\nsigs.k8s.io/controller-runtime v0.17.0/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s=\nsigs.k8s.io/controller-tools v0.13.0 h1:NfrvuZ4bxyolhDBt/rCZhDnx3M2hzlhgo5n3Iv2RykI=\nsigs.k8s.io/controller-tools v0.13.0/go.mod h1:5vw3En2NazbejQGCeWKRrE7q4P+CW8/klfVqP8QZkgA=\nsigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=\nsigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=\nsigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=\nsigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=\nsigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=\nsigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=\n"
  },
  {
    "path": "pkg/apis/lockbox.k8s.cloudflare.com/v1/groupversion_info.go",
    "content": "// +kubebuilder:object:generate=true\n// +groupName=lockbox.k8s.cloudflare.com\n\n// Package v1 is the v1 version of the Lockbox API\npackage v1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\n//go:generate controller-gen object crd paths=./. output:crd:artifacts:config=../../../../deployment/crds\n\nvar (\n\t// GroupVersion is group version used to register these objects\n\tGroupVersion = schema.GroupVersion{Group: \"lockbox.k8s.cloudflare.com\", Version: \"v1\"}\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme\n\tSchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n\nfunc init() {\n\tSchemeBuilder.Register(&Lockbox{}, &LockboxList{})\n}\n"
  },
  {
    "path": "pkg/apis/lockbox.k8s.cloudflare.com/v1/lockbox.go",
    "content": "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\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nconst keySize = nacl.KeySize\n\n// NewFromSecret creates a Lockbox wrapping the provided Secret. The value of each secret\n// are individually encrypted using the provided key pair.\nfunc NewFromSecret(secret corev1.Secret, namespace string, peer, pub, pri nacl.Key) *Lockbox {\n\tencNS := box.EasySeal([]byte(namespace), peer, pri)\n\n\tb := &Lockbox{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      secret.Name,\n\t\t\tNamespace: namespace,\n\t\t},\n\t\tSpec: LockboxSpec{\n\t\t\tSender:    pub[:],\n\t\t\tPeer:      peer[:],\n\t\t\tNamespace: encNS,\n\t\t\tData:      map[string][]byte{},\n\t\t\tTemplate: LockboxSecretTemplate{\n\t\t\t\tLockboxSecretTemplateMetadata: LockboxSecretTemplateMetadata{\n\t\t\t\t\tLabels:      secret.ObjectMeta.Labels,\n\t\t\t\t\tAnnotations: secret.ObjectMeta.Annotations,\n\t\t\t\t},\n\t\t\t\tType: secret.Type,\n\t\t\t},\n\t\t},\n\t}\n\n\tfor key, value := range secret.Data {\n\t\tenc := box.EasySeal(value, peer, pri)\n\t\tb.Spec.Data[key] = enc\n\t}\n\n\tfor key, value := range secret.StringData {\n\t\tenc := box.EasySeal([]byte(value), peer, pri)\n\t\tb.Spec.Data[key] = enc\n\t}\n\n\treturn b\n}\n\n// UnlockInto decrypts each secret value into the provided secret.\nfunc (in *Lockbox) UnlockInto(secret *corev1.Secret, pri nacl.Key) error {\n\tsender := new([keySize]byte)\n\tcopy(sender[:], in.Spec.Sender)\n\n\tdata := make(map[string][]byte, len(in.Spec.Data))\n\tfor key, val := range in.Spec.Data {\n\t\td, err := box.EasyOpen(val, sender, pri)\n\t\tif err != nil {\n\t\t\treturn decryptSecretKeyError{error: err, key: key}\n\t\t}\n\n\t\tdata[key] = d\n\t}\n\n\tsecret.Data = data\n\tsecret.Type = in.Spec.Template.Type\n\tsecret.Labels = in.Spec.Template.Labels\n\tsecret.Annotations = in.Spec.Template.Annotations\n\n\treturn nil\n}\n\n// decryptSecretKeyError wraps error while decrypting data from a secret.\n// This allows preserving the key for farther error messages.\ntype decryptSecretKeyError struct {\n\terror\n\tkey string\n}\n\n// SecretKey returns the secret data key that triggered this error.\nfunc (e decryptSecretKeyError) SecretKey() string {\n\treturn e.key\n}\n\n// Unwrap implements Wrapper, returning the underlying error message.\nfunc (e decryptSecretKeyError) Unwrap() error {\n\treturn e.error\n}\n\nfunc (in *Lockbox) GetConditions() []Condition {\n\treturn in.Status.Conditions\n}\n\nfunc (in *Lockbox) SetConditions(conditions []Condition) {\n\tin.Status.Conditions = conditions\n}\n"
  },
  {
    "path": "pkg/apis/lockbox.k8s.cloudflare.com/v1/lockbox_test.go",
    "content": "package v1_test\n\nimport (\n\t\"crypto/rand\"\n\t\"testing\"\n\n\tv1 \"github.com/cloudflare/lockbox/pkg/apis/lockbox.k8s.cloudflare.com/v1\"\n\t\"github.com/kevinburke/nacl\"\n\t\"github.com/kevinburke/nacl/box\"\n\t\"gotest.tools/v3/assert\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc TestUnlock(t *testing.T) {\n\t_, priKey, err := loadKeypair(t, \"6a42b9fc2b011fb88c01741483e3bffe455bdab1ae35d0bb53a3c00d406d8836\", \"252173f975f0a0ddb198a7e5958c074203a0e9f44275e0b840f95d456c4acc2e\")\n\tassert.NilError(t, err)\n\n\tlb := &v1.Lockbox{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"example\",\n\t\t\tNamespace: \"example\",\n\t\t\tLabels: map[string]string{\n\t\t\t\t\"type\": \"lockbox\",\n\t\t\t},\n\t\t\tAnnotations: map[string]string{\n\t\t\t\t\"helm.sh/hook\": \"pre-install\",\n\t\t\t},\n\t\t},\n\t\tSpec: v1.LockboxSpec{\n\t\t\tSender:    []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},\n\t\t\tPeer:      []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},\n\t\t\tNamespace: []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},\n\t\t\tTemplate: v1.LockboxSecretTemplate{\n\t\t\t\tLockboxSecretTemplateMetadata: v1.LockboxSecretTemplateMetadata{\n\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\"type\": \"secret\",\n\t\t\t\t\t},\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\t\"wave\": \"ignore\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tData: map[string][]byte{\n\t\t\t\t\"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},\n\t\t\t\t\"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},\n\t\t\t},\n\t\t},\n\t}\n\n\tsecret := &corev1.Secret{}\n\texpected := &corev1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tLabels: map[string]string{\n\t\t\t\t\"type\": \"secret\",\n\t\t\t},\n\t\t\tAnnotations: map[string]string{\n\t\t\t\t\"wave\": \"ignore\",\n\t\t\t},\n\t\t},\n\t\tData: map[string][]byte{\n\t\t\t\"test\":  {0x74, 0x65, 0x73, 0x74},\n\t\t\t\"test1\": {0x74, 0x65, 0x73, 0x74, 0x31},\n\t\t},\n\t}\n\n\tassert.NilError(t, lb.UnlockInto(secret, priKey))\n\tassert.DeepEqual(t, secret, expected)\n}\n\nfunc TestUnlockErr(t *testing.T) {\n\t_, priKey, err := loadKeypair(t, \"6a42b9fc2b011fb88c01741483e3bffe455bdab1ae35d0bb53a3c00d406d8836\", \"252173f975f0a0ddb198a7e5958c074203a0e9f44275e0b840f95d456c4acc2e\")\n\tassert.NilError(t, err)\n\n\tlb := &v1.Lockbox{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"example\",\n\t\t\tNamespace: \"example\",\n\t\t\tLabels: map[string]string{\n\t\t\t\t\"type\": \"lockbox\",\n\t\t\t},\n\t\t\tAnnotations: map[string]string{\n\t\t\t\t\"helm.sh/hook\": \"pre-install\",\n\t\t\t},\n\t\t},\n\t\tSpec: v1.LockboxSpec{\n\t\t\tSender:    []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},\n\t\t\tPeer:      []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},\n\t\t\tNamespace: []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},\n\t\t\tTemplate: v1.LockboxSecretTemplate{\n\t\t\t\tLockboxSecretTemplateMetadata: v1.LockboxSecretTemplateMetadata{\n\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\"type\": \"secret\",\n\t\t\t\t\t},\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\t\"wave\": \"ignore\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tData: map[string][]byte{\n\t\t\t\t\"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},\n\t\t\t\t\"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},\n\t\t\t},\n\t\t},\n\t}\n\n\tsecret := &corev1.Secret{}\n\texpected := &corev1.Secret{}\n\n\terr = lb.UnlockInto(secret, priKey)\n\tassert.ErrorContains(t, err, \"Could not decrypt invalid input\")\n\tassert.DeepEqual(t, secret, expected)\n}\n\nfunc TestLockUnlock(t *testing.T) {\n\tsenderPubKey, senderPriKey, _ := box.GenerateKey(rand.Reader)\n\tserverPubKey, serverPriKey, _ := box.GenerateKey(rand.Reader)\n\n\tsecret := corev1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tLabels: map[string]string{\n\t\t\t\t\"type\": \"secret\",\n\t\t\t},\n\t\t},\n\t\tData: map[string][]byte{\n\t\t\t\"test\": {0x74, 0x65, 0x73, 0x74},\n\t\t},\n\t}\n\n\tlb := v1.NewFromSecret(secret, \"namespace\", serverPubKey, senderPubKey, senderPriKey)\n\tunlockedSecret := &corev1.Secret{}\n\texpectedSecret := &corev1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tLabels: map[string]string{\n\t\t\t\t\"type\": \"secret\",\n\t\t\t},\n\t\t},\n\t\tData: map[string][]byte{\n\t\t\t\"test\": {0x74, 0x65, 0x73, 0x74},\n\t\t},\n\t}\n\n\tassert.NilError(t, lb.UnlockInto(unlockedSecret, serverPriKey))\n\tassert.DeepEqual(t, unlockedSecret, expectedSecret)\n}\n\nfunc loadKeypair(t *testing.T, pub, pri string) (pubKey, priKey nacl.Key, err error) {\n\tt.Helper()\n\n\tpubKey, err = nacl.Load(pub)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tpriKey, err = nacl.Load(pri)\n\treturn\n}\n"
  },
  {
    "path": "pkg/apis/lockbox.k8s.cloudflare.com/v1/types.go",
    "content": "package v1\n\nimport (\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n// +kubebuilder:printcolumn:name=\"SecretType\",type=string,JSONPath=`.spec.template.type`\n// +kubebuilder:printcolumn:name=\"Peer\",type=string,JSONPath=`.spec.peer`\n\n// Lockbox is a struct wrapping the LockboxSpec in standard API server\n// metadata fields.\ntype Lockbox struct {\n\tmetav1.TypeMeta   `json:\",inline\"`\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\t// Desired state of the Lockbox resource.\n\tSpec LockboxSpec `json:\"spec\"`\n\n\t// Status of the Lockbox. This is set and managed automatically.\n\t// +optional\n\tStatus LockboxStatus `json:\"status,omitempty\"`\n}\n\n// LockboxSpec is a struct wrapping the encrypted secrets along with the\n// public keys of the sender and server.\ntype LockboxSpec struct {\n\t// Sender stores the public key used to lock this Lockbox.\n\tSender []byte `json:\"sender\"`\n\n\t// Peer stores the public key that can unlock this Lockbox.\n\tPeer []byte `json:\"peer\"`\n\n\t// Namespace stores an encrypted copy of which namespace this Lockbox is locked\n\t// for, ensuring it cannot be deployed to another namespace under an attacker's\n\t// control.\n\tNamespace []byte `json:\"namespace\"`\n\n\t// Data contains the secret data, encrypted to the Peer's public key. Each key in the\n\t// data map must consist of alphanumeric characters, '-', '_', or '.'.\n\tData map[string][]byte `json:\"data\"`\n\n\t// Template defines the structure of the Secret that will be\n\t// created from this Lockbox.\n\t// +optional\n\tTemplate LockboxSecretTemplate `json:\"template,omitempty\"`\n}\n\n// LockboxSecretTemplate defines structure of API metadata fields\n// of Secrets controlled by a Lockbox.\ntype LockboxSecretTemplate struct {\n\tLockboxSecretTemplateMetadata `json:\"metadata,omitempty\"`\n\n\t// Type is used to facilitate programmatic handling of secret data.\n\tType corev1.SecretType `json:\"type,omitempty\"`\n}\n\ntype LockboxSecretTemplateMetadata struct {\n\t// Map of string keys and values that can be used to organize and categorize\n\t// (scope and select) objects. May match selectors of replication\n\t// controllers and services. More info:\n\t// http://kubernetes.io/docs/user-guide/labels\n\t// +optional\n\tLabels map[string]string `json:\"labels,omitempty\"`\n\n\t// Annotations is an unstructured key value map stored with a resource that\n\t// may be set by external tools to store and retrieve arbitrary metadata.\n\t// They are not queryable and should be preserved when modifying objects.\n\t// More info: http://kubernetes.io/docs/user-guide/annotations\n\t// +optional\n\tAnnotations map[string]string `json:\"annotations,omitempty\"`\n}\n\n// LockboxStatus contains status information about a Lockbox.\ntype LockboxStatus struct {\n\t// List of status conditions to indicate the status of a Lockbox.\n\t// +optional\n\tConditions []Condition `json:\"conditions,omitempty\"`\n}\n\n// Condition contains condition information for a Lockbox.\ntype Condition struct {\n\t// Type of condition in CamelCase.\n\t// +required\n\tType ConditionType `json:\"type\"`\n\n\t// Status of the condition, one of True, False, Unknown\n\t// +required\n\tStatus corev1.ConditionStatus `json:\"status\"`\n\n\t// Severity provides explicit classification of Reason code, so that users or machines\n\t// can immediately understand the current situation and act accordingly.\n\t// The Severity field MUST be set only when Status=False.\n\t// +optional\n\tSeverity ConditionSeverity `json:\"severity\"`\n\n\t// LastTransitionTime marks when the condition last transitioned from one status to another.\n\t// This should be when the underlying condition changed. If that is not known, then using the time\n\t// when the API field changed is acceptable.\n\t// +required\n\tLastTransitionTime metav1.Time `json:\"lastTransitionTime,omitempty\"`\n\n\t// The reason for the condition's last transition in CamelCase.\n\t// +optional\n\tReason string `json:\"reason,omitempty\"`\n\n\t// A message is the human readable message indicating details about the transition.\n\t// The field may be empty.\n\t// +optional\n\tMessage string `json:\"message,omitempty\"`\n}\n\n// +kubebuilder:validation:Enum=Ready\ntype ConditionType string\n\nconst (\n\tReadyCondition ConditionType = \"Ready\"\n)\n\n// +kubebuilder:validation:Enum=Error;Warning;Info\ntype ConditionSeverity string\n\nconst (\n\tConditionSeverityError   ConditionSeverity = \"Error\"\n\tConditionSeverityWarning ConditionSeverity = \"Warning\"\n\tConditionSeverityInfo    ConditionSeverity = \"Info\"\n\tConditionSeverityNone    ConditionSeverity = \"\"\n)\n\n// +kubebuilder:object:root=true\n\n// LockboxList is a Lockbox-specific version of metav1.List.\ntype LockboxList struct {\n\tmetav1.TypeMeta\n\tmetav1.ListMeta\n\n\tItems []Lockbox\n}\n"
  },
  {
    "path": "pkg/apis/lockbox.k8s.cloudflare.com/v1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v1\n\nimport (\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Condition) DeepCopyInto(out *Condition) {\n\t*out = *in\n\tin.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition.\nfunc (in *Condition) DeepCopy() *Condition {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Condition)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *Lockbox) DeepCopyInto(out *Lockbox) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Lockbox.\nfunc (in *Lockbox) DeepCopy() *Lockbox {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Lockbox)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *Lockbox) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *LockboxList) DeepCopyInto(out *LockboxList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]Lockbox, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LockboxList.\nfunc (in *LockboxList) DeepCopy() *LockboxList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(LockboxList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *LockboxList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *LockboxSecretTemplate) DeepCopyInto(out *LockboxSecretTemplate) {\n\t*out = *in\n\tin.LockboxSecretTemplateMetadata.DeepCopyInto(&out.LockboxSecretTemplateMetadata)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LockboxSecretTemplate.\nfunc (in *LockboxSecretTemplate) DeepCopy() *LockboxSecretTemplate {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(LockboxSecretTemplate)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *LockboxSecretTemplateMetadata) DeepCopyInto(out *LockboxSecretTemplateMetadata) {\n\t*out = *in\n\tif in.Labels != nil {\n\t\tin, out := &in.Labels, &out.Labels\n\t\t*out = make(map[string]string, len(*in))\n\t\tfor key, val := range *in {\n\t\t\t(*out)[key] = val\n\t\t}\n\t}\n\tif in.Annotations != nil {\n\t\tin, out := &in.Annotations, &out.Annotations\n\t\t*out = make(map[string]string, len(*in))\n\t\tfor key, val := range *in {\n\t\t\t(*out)[key] = val\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LockboxSecretTemplateMetadata.\nfunc (in *LockboxSecretTemplateMetadata) DeepCopy() *LockboxSecretTemplateMetadata {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(LockboxSecretTemplateMetadata)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *LockboxSpec) DeepCopyInto(out *LockboxSpec) {\n\t*out = *in\n\tif in.Sender != nil {\n\t\tin, out := &in.Sender, &out.Sender\n\t\t*out = make([]byte, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.Peer != nil {\n\t\tin, out := &in.Peer, &out.Peer\n\t\t*out = make([]byte, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.Namespace != nil {\n\t\tin, out := &in.Namespace, &out.Namespace\n\t\t*out = make([]byte, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.Data != nil {\n\t\tin, out := &in.Data, &out.Data\n\t\t*out = make(map[string][]byte, len(*in))\n\t\tfor key, val := range *in {\n\t\t\tvar outVal []byte\n\t\t\tif val == nil {\n\t\t\t\t(*out)[key] = nil\n\t\t\t} else {\n\t\t\t\tinVal := (*in)[key]\n\t\t\t\tin, out := &inVal, &outVal\n\t\t\t\t*out = make([]byte, len(*in))\n\t\t\t\tcopy(*out, *in)\n\t\t\t}\n\t\t\t(*out)[key] = outVal\n\t\t}\n\t}\n\tin.Template.DeepCopyInto(&out.Template)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LockboxSpec.\nfunc (in *LockboxSpec) DeepCopy() *LockboxSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(LockboxSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *LockboxStatus) DeepCopyInto(out *LockboxStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LockboxStatus.\nfunc (in *LockboxStatus) DeepCopy() *LockboxStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(LockboxStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "pkg/flagvar/enum.go",
    "content": "package flagvar\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n)\n\nvar ErrInvalidEnum = errors.New(\"invalid enum option\")\n\ntype Enum struct {\n\tChoices []string\n\tValue   string\n}\n\nfunc (e *Enum) Help() string {\n\treturn fmt.Sprintf(\"one of %v\", e.Choices)\n}\n\nfunc (e *Enum) Set(v string) error {\n\tfor _, c := range e.Choices {\n\t\tif strings.EqualFold(c, v) {\n\t\t\te.Value = strings.ToLower(v)\n\t\t\treturn nil\n\t\t}\n\t}\n\n\treturn ErrInvalidEnum\n}\n\nfunc (e *Enum) String() string {\n\tif e == nil {\n\t\treturn \"\"\n\t}\n\n\treturn e.Value\n}\n"
  },
  {
    "path": "pkg/flagvar/enum_test.go",
    "content": "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\nfunc TestEnumString(t *testing.T) {\n\ttype testCase struct {\n\t\tname     string\n\t\tfv       *flagvar.Enum\n\t\texpected string\n\t}\n\n\trun := func(t *testing.T, tc testCase) {\n\t\tactual := tc.fv.String()\n\t\tassert.Equal(t, actual, tc.expected)\n\t}\n\n\ttestCases := []testCase{\n\t\t{\n\t\t\tname:     \"non-nil receiver\",\n\t\t\tfv:       &flagvar.Enum{Value: \"yaml\"},\n\t\t\texpected: \"yaml\",\n\t\t},\n\t\t{\n\t\t\tname:     \"nil receiver\",\n\t\t\tfv:       nil,\n\t\t\texpected: \"\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trun(t, tc)\n\t\t})\n\t}\n}\n\nfunc TestEnumSet(t *testing.T) {\n\ttype testCase struct {\n\t\tname     string\n\t\tinput    string\n\t\texpected string\n\t\terr      error\n\t}\n\n\trun := func(t *testing.T, tc testCase) {\n\t\tfv := &flagvar.Enum{\n\t\t\tChoices: []string{\"yaml\", \"json\"},\n\t\t}\n\t\terr := fv.Set(tc.input)\n\n\t\tif err != nil {\n\t\t\tassert.ErrorIs(t, err, tc.err)\n\t\t} else {\n\t\t\tassert.Equal(t, fv.Value, tc.expected)\n\t\t}\n\t}\n\n\ttestCases := []testCase{\n\t\t{\n\t\t\tname:     \"valid enum option\",\n\t\t\tinput:    \"yaml\",\n\t\t\texpected: \"yaml\",\n\t\t},\n\t\t{\n\t\t\tname:     \"ignores option capitalization\",\n\t\t\tinput:    \"YaMl\",\n\t\t\texpected: \"yaml\",\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid enum option\",\n\t\t\tinput: \"cue\",\n\t\t\terr:   flagvar.ErrInvalidEnum,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trun(t, tc)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/flagvar/file.go",
    "content": "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 struct {\n\tValue string\n}\n\n// Help returns a string to include in the flag's help message.\nfunc (f *File) Help() string {\n\treturn \"file path\"\n}\n\n// Set implements flag.Value by checking for the file's existence through\n// using os.Stat. Any error returned by os.Stat is returned by this function.\nfunc (f *File) Set(v string) error {\n\t_, err := os.Stat(v)\n\tf.Value = v\n\n\treturn err\n}\n\n// String implements flag.Value by returning the current file path.\nfunc (f *File) String() string {\n\tif f == nil {\n\t\treturn \"\"\n\t}\n\n\treturn f.Value\n}\n\n// Type implements pflag.Value by noting our Value is string typed.\nfunc (f *File) Type() string {\n\treturn \"string\"\n}\n"
  },
  {
    "path": "pkg/flagvar/file_test.go",
    "content": "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\"gotest.tools/v3/assert\"\n)\n\nfunc TestFileString(t *testing.T) {\n\ttype testCase struct {\n\t\tname     string\n\t\tfv       *flagvar.File\n\t\texpected string\n\t}\n\n\trun := func(t *testing.T, tc testCase) {\n\t\tactual := tc.fv.String()\n\t\tassert.Equal(t, actual, tc.expected)\n\t}\n\n\ttestCases := []testCase{\n\t\t{\n\t\t\tname:     \"non-nil receiver\",\n\t\t\tfv:       &flagvar.File{Value: \"/path/to/default.log\"},\n\t\t\texpected: \"/path/to/default.log\",\n\t\t},\n\t\t{\n\t\t\tname:     \"nil receiver\",\n\t\t\tfv:       nil,\n\t\t\texpected: \"\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trun(t, tc)\n\t\t})\n\t}\n}\n\nfunc TestFileSet(t *testing.T) {\n\ttype testCase struct {\n\t\tname     string\n\t\tinput    string\n\t\texpected string\n\t\terr      error\n\t}\n\n\trun := func(t *testing.T, tc testCase) {\n\t\tfv := &flagvar.File{}\n\n\t\terr := fv.Set(tc.input)\n\t\tif tc.err != nil {\n\t\t\tassert.ErrorIs(t, err, tc.err)\n\t\t} else {\n\t\t\tassert.Equal(t, fv.Value, tc.expected)\n\t\t}\n\t}\n\n\ttestCases := []testCase{\n\t\t{\n\t\t\tname:     \"file exists\",\n\t\t\tinput:    filepath.Join(\"testdata\", \"file\"),\n\t\t\texpected: \"testdata/file\",\n\t\t},\n\t\t{\n\t\t\tname:  \"file does not exist\",\n\t\t\tinput: filepath.Join(\"testdata\", \"file_nonexistant.go\"),\n\t\t\terr:   fs.ErrNotExist,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trun(t, tc)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/flagvar/tcp_addr.go",
    "content": "package flagvar\n\nimport \"net\"\n\n// TCPAddr is a flag.Value for file paths. Returns any errors from net.ResolveTCPAddr.\ntype TCPAddr struct {\n\tNetwork string\n\tValue   *net.TCPAddr\n\tText    string\n}\n\n// Help returns a string to include in the flag's help message.\nfunc (t *TCPAddr) Help() string {\n\treturn \"TCP address in host:port format\"\n}\n\n// Set implements flag.Value by parsing the provided address using net.ResolveTCPAddr.\n// Any error return is returned by this function.\nfunc (t *TCPAddr) Set(v string) error {\n\tnetwork := \"tcp\"\n\tif t.Network != \"\" {\n\t\tnetwork = t.Network\n\t}\n\n\ttcpAddr, err := net.ResolveTCPAddr(network, v)\n\tt.Text = v\n\tt.Value = tcpAddr\n\n\treturn err\n}\n\n// String implements flag.Value by returning the current Text.\nfunc (t *TCPAddr) String() string {\n\tif t == nil {\n\t\treturn \"\"\n\t}\n\n\treturn t.Text\n}\n\n// Type implements pflag.Value by noting our Value is net.TCPAddr typed.\nfunc (t *TCPAddr) Type() string {\n\treturn \"net.TCPAddr\"\n}\n"
  },
  {
    "path": "pkg/flagvar/tcp_addr_test.go",
    "content": "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\"\n)\n\nfunc TestTCPAddrString(t *testing.T) {\n\ttype testCase struct {\n\t\tname     string\n\t\tfv       *flagvar.TCPAddr\n\t\texpected string\n\t}\n\n\trun := func(t *testing.T, tc testCase) {\n\t\tactual := tc.fv.String()\n\t\tassert.Equal(t, actual, tc.expected)\n\t}\n\n\ttestCases := []testCase{\n\t\t{\n\t\t\tname:     \"non-nil receiver\",\n\t\t\tfv:       &flagvar.TCPAddr{Text: \":8080\"},\n\t\t\texpected: \":8080\",\n\t\t},\n\t\t{\n\t\t\tname:     \"nil receiver\",\n\t\t\tfv:       nil,\n\t\t\texpected: \"\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trun(t, tc)\n\t\t})\n\t}\n}\n\nfunc TestTCPAddrSet(t *testing.T) {\n\ttype testCase struct {\n\t\tname     string\n\t\tinput    string\n\t\texpected *net.TCPAddr\n\t\terr      string\n\t}\n\n\trun := func(t *testing.T, tc testCase) {\n\t\tfv := flagvar.TCPAddr{}\n\t\terr := fv.Set(tc.input)\n\n\t\tif err != nil {\n\t\t\tassert.Error(t, err, tc.err)\n\t\t} else {\n\t\t\tassert.DeepEqual(t, fv.Value, tc.expected)\n\t\t}\n\t}\n\n\ttestCases := []testCase{\n\t\t{\n\t\t\tname:  \"host:port address\",\n\t\t\tinput: \"127.0.0.1:8080\",\n\t\t\texpected: &net.TCPAddr{\n\t\t\t\tIP:   net.ParseIP(\"127.0.0.1\"),\n\t\t\t\tPort: 8080,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"port-only address\",\n\t\t\tinput: \":8080\",\n\t\t\texpected: &net.TCPAddr{\n\t\t\t\tPort: 8080,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"IPv6 support\",\n\t\t\tinput: \"[::1]:8080\",\n\t\t\texpected: &net.TCPAddr{\n\t\t\t\tIP:   net.ParseIP(\"::1\"),\n\t\t\t\tPort: 8080,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"invalid address\",\n\t\t\tinput:    \"google.com\",\n\t\t\texpected: nil,\n\t\t\terr:      \"address google.com: missing port in address\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trun(t, tc)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/flagvar/testdata/file",
    "content": ""
  },
  {
    "path": "pkg/lockbox-controller/secretreconciler.go",
    "content": "package controller\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\n\tlockboxv1 \"github.com/cloudflare/lockbox/pkg/apis/lockbox.k8s.cloudflare.com/v1\"\n\t\"github.com/cloudflare/lockbox/pkg/util/conditions\"\n\t\"github.com/kevinburke/nacl\"\n\t\"github.com/kevinburke/nacl/box\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/tools/record\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tclientfake \"sigs.k8s.io/controller-runtime/pkg/client/fake\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n)\n\n//go:generate controller-gen rbac:roleName=lockbox-controller paths=./. output:rbac:artifacts:config=../../deployment/rbac\n\n// +kubebuilder:rbac:groups=\"\",resources=secrets,verbs=get;list;watch;create;patch;update\n// +kubebuilder:rbac:groups=\"\",resources=events,verbs=create;patch\n// +kubebuilder:rbac:groups=\"lockbox.k8s.cloudflare.com\",resources=lockboxes,verbs=get;list;watch\n// +kubebuilder:rbac:groups=\"lockbox.k8s.cloudflare.com\",resources=lockboxes/status,verbs=get;update;patch\n\nconst keySize = nacl.KeySize\n\n// SecretReconcilerOption allows for functional options to modify the SecretReconciler\ntype SecretReconcilerOption func(s *SecretReconciler)\n\n// SecretReconciler implements the reconciliation logic for Lockbox secrets.\ntype SecretReconciler struct {\n\tpubKey, priKey nacl.Key\n\n\tclient   client.Client\n\trecorder record.EventRecorder\n}\n\n// NewSecretReconciler creates a reconciler controller for the provided keypair and options.\n//\n// If not mutated by any options, the reconciler uses a noop API client and events recorder.\nfunc NewSecretReconciler(pubKey, priKey nacl.Key, options ...SecretReconcilerOption) *SecretReconciler {\n\tsr := &SecretReconciler{\n\t\tpubKey:   pubKey,\n\t\tpriKey:   priKey,\n\t\tclient:   clientfake.NewClientBuilder().Build(),\n\t\trecorder: &record.FakeRecorder{},\n\t}\n\n\tfor _, opt := range options {\n\t\topt(sr)\n\t}\n\n\treturn sr\n}\n\n// Reconcile implements reconcile.Reconciler by ensuring Lockbox controlled Secrets are as described.\nfunc (s *SecretReconciler) Reconcile(ctx context.Context, lb *lockboxv1.Lockbox) (reconcile.Result, error) {\n\tif len(lb.Spec.Sender) != keySize {\n\t\tmsg := fmt.Sprintf(\"invalid sender key length, got %d wanted %d\", len(lb.Spec.Sender), keySize)\n\n\t\ts.recorder.Eventf(lb, \"Warning\", \"InvalidKeyLength\", msg)\n\t\tconditions.Set(lb, conditions.FalseCondition(lockboxv1.ReadyCondition, \"InvalidKeyLength\", lockboxv1.ConditionSeverityError, msg))\n\t\t_ = s.client.Status().Update(ctx, lb)\n\t\treturn reconcile.Result{}, fmt.Errorf(\"incorrect sender key length: %d, should be %d\", len(lb.Spec.Sender), keySize)\n\t}\n\tif len(lb.Spec.Peer) != keySize {\n\t\tmsg := fmt.Sprintf(\"invalid peer key length, got %d wanted %d\", len(lb.Spec.Peer), keySize)\n\n\t\ts.recorder.Eventf(lb, \"Warning\", \"InvalidKeyLength\", msg)\n\t\tconditions.Set(lb, conditions.FalseCondition(lockboxv1.ReadyCondition, \"InvalidKeyLength\", lockboxv1.ConditionSeverityError, msg))\n\t\t_ = s.client.Status().Update(ctx, lb)\n\t\treturn reconcile.Result{}, fmt.Errorf(\"incorrect peer key length: %d, should be %d\", len(lb.Spec.Peer), keySize)\n\t}\n\n\tpeerKey := new([keySize]byte)\n\tcopy(peerKey[:], lb.Spec.Peer)\n\n\tif !nacl.Verify32(peerKey, s.pubKey) {\n\t\tmsg := fmt.Sprintf(\"lockbox has unknown peer key %q\", base64.StdEncoding.EncodeToString(lb.Spec.Peer))\n\n\t\ts.recorder.Eventf(lb, \"Warning\", \"UnknownPeerKey\", msg)\n\t\tconditions.Set(lb, conditions.FalseCondition(lockboxv1.ReadyCondition, \"UnknownPeerKey\", lockboxv1.ConditionSeverityError, msg))\n\t\t_ = s.client.Status().Update(ctx, lb)\n\t\treturn reconcile.Result{}, fmt.Errorf(\"unknown peer key\")\n\t}\n\n\tsender := new([keySize]byte)\n\tcopy(sender[:], lb.Spec.Sender)\n\n\tsecret := &corev1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      lb.Name,\n\t\t\tNamespace: lb.Namespace,\n\t\t},\n\t}\n\n\tnamespace, err := box.EasyOpen(lb.Spec.Namespace, sender, s.priKey)\n\tif err != nil {\n\t\tmsg := fmt.Sprintf(\"unable to open lockbox with peer key %q\", base64.StdEncoding.EncodeToString(lb.Spec.Peer))\n\n\t\ts.recorder.Eventf(lb, \"Warning\", \"InvalidLockbox\", msg)\n\t\tconditions.Set(lb, conditions.FalseCondition(lockboxv1.ReadyCondition, \"InvalidLockbox\", lockboxv1.ConditionSeverityError, msg))\n\t\t_ = s.client.Status().Update(ctx, lb)\n\t\treturn reconcile.Result{}, err\n\t}\n\n\tif string(namespace) != lb.Namespace {\n\t\tmsg := fmt.Sprintf(\"locked for namespace %q, found in namespace %s\", namespace, lb.Namespace)\n\n\t\ts.recorder.Eventf(lb, \"Warning\", \"InvalidNamespace\", msg)\n\t\tconditions.Set(lb, conditions.FalseCondition(lockboxv1.ReadyCondition, \"InvalidNamespace\", lockboxv1.ConditionSeverityWarning, msg))\n\t\t_ = s.client.Status().Update(ctx, lb)\n\t\treturn reconcile.Result{}, fmt.Errorf(\"incorrect namespace: %s, should be %s\", namespace, lb.Namespace)\n\t}\n\n\t_, err = controllerutil.CreateOrPatch(\n\t\tctx,\n\t\ts.client,\n\t\tsecret,\n\t\ts.reconcileExisting(lb, sender, secret))\n\n\tif err != nil {\n\t\tconditions.Set(lb, conditions.FalseCondition(lockboxv1.ReadyCondition, \"InvalidLockbox\", lockboxv1.ConditionSeverityWarning, err.Error()))\n\t\t_ = s.client.Status().Update(ctx, lb)\n\t\treturn reconcile.Result{}, err\n\t}\n\n\tconditions.Set(lb, conditions.TrueCondition(lockboxv1.ReadyCondition))\n\t_ = s.client.Status().Update(ctx, lb)\n\treturn reconcile.Result{}, nil\n}\n\n// reconcileExisting returns a function suitable for controllerutil.CreateOrUpdate that mutates a Secret object\n// to reflect the desired state.\nfunc (s *SecretReconciler) reconcileExisting(lb *lockboxv1.Lockbox, sender nacl.Key, secret *corev1.Secret) func() error {\n\treturn func() error {\n\t\tif err := controllerutil.SetControllerReference(lb, secret, s.client.Scheme()); err != nil {\n\t\t\tswitch err := err.(type) {\n\t\t\tcase decryptSecretKeyErrorer:\n\t\t\t\ts.recorder.Eventf(lb, \"Warning\", \"InvalidLockbox\", \"lockbox contained key %q that could not be unlocked\", err.SecretKey())\n\t\t\tdefault:\n\t\t\t\ts.recorder.Eventf(lb, \"Warning\", \"InvalidLockbox\", \"lockbox could not be unlocked\")\n\t\t\t}\n\n\t\t\treturn err\n\t\t}\n\n\t\treturn lb.UnlockInto(secret, s.priKey)\n\t}\n}\n\n// WithRecorder sets the EventRecorder used by the SecretReconciler.\nfunc WithRecorder(r record.EventRecorder) SecretReconcilerOption {\n\treturn func(s *SecretReconciler) {\n\t\ts.recorder = r\n\t}\n}\n\n// WithClient sets the API Client used by the SecretReconciler\nfunc WithClient(c client.Client) SecretReconcilerOption {\n\treturn func(s *SecretReconciler) {\n\t\ts.client = c\n\t}\n}\n\n// decryptSecretKeyErrorer matches the unexported error type, to\n// fetch the secret data key that triggered the error.\ntype decryptSecretKeyErrorer interface {\n\tSecretKey() string\n}\n"
  },
  {
    "path": "pkg/lockbox-controller/secretreconciler_suite_test.go",
    "content": "//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\"testing\"\n\n\tlockboxv1 \"github.com/cloudflare/lockbox/pkg/apis/lockbox.k8s.cloudflare.com/v1\"\n\t. \"github.com/cloudflare/lockbox/pkg/lockbox-controller\"\n\t\"github.com/go-logr/zerologr\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/rs/zerolog\"\n\t\"gotest.tools/v3/assert\"\n\t\"gotest.tools/v3/poll\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/utils/ptr\"\n\t\"sigs.k8s.io/controller-runtime/pkg/builder\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/manager\"\n\tmetricsserver \"sigs.k8s.io/controller-runtime/pkg/metrics/server\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n)\n\nvar cfg *rest.Config\n\nfunc TestMain(m *testing.M) {\n\tzl := zerolog.New(os.Stderr)\n\tlogf.SetLogger(zerologr.New(&zl))\n\tt := &envtest.Environment{\n\t\tCRDDirectoryPaths: []string{filepath.Join(\"..\", \"..\", \"deployment\", \"crds\")},\n\t}\n\tlockboxv1.AddToScheme(scheme.Scheme)\n\n\tvar err error\n\tif cfg, err = t.Start(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tcode := m.Run()\n\tt.Stop()\n\tos.Exit(code)\n}\n\nfunc TestSuiteSecretReconciler(t *testing.T) {\n\ttype testCase struct {\n\t\tname        string\n\t\tlockboxName string\n\t\tresources   []client.Object\n\t\texpected    *corev1.Secret\n\t}\n\n\tpubKey, priKey, err := loadKeypair(t, \"6a42b9fc2b011fb88c01741483e3bffe455bdab1ae35d0bb53a3c00d406d8836\", \"252173f975f0a0ddb198a7e5958c074203a0e9f44275e0b840f95d456c4acc2e\")\n\tassert.NilError(t, err)\n\n\tsetup := func(t *testing.T, tc testCase) {\n\t\tmgr, err := manager.New(cfg, manager.Options{\n\t\t\tMetrics: metricsserver.Options{\n\t\t\t\tBindAddress: \"0\",\n\t\t\t},\n\t\t\tScheme: scheme.Scheme,\n\t\t})\n\t\tassert.NilError(t, err)\n\n\t\tsr := NewSecretReconciler(pubKey, priKey, WithClient(mgr.GetClient()))\n\t\terr = builder.\n\t\t\tControllerManagedBy(mgr).\n\t\t\tFor(&lockboxv1.Lockbox{}).\n\t\t\tOwns(&corev1.Secret{}).\n\t\t\tComplete(reconcile.AsReconciler(mgr.GetClient(), sr))\n\t\tassert.NilError(t, err)\n\n\t\tctx, cancel := context.WithCancel(context.Background())\n\t\tgo func() {\n\t\t\tmgr.Start(ctx)\n\t\t}()\n\t\tt.Cleanup(cancel)\n\t}\n\n\trun := func(t *testing.T, tc testCase) {\n\t\tc, err := client.New(cfg, client.Options{})\n\t\tassert.NilError(t, err)\n\n\t\tfor _, r := range tc.resources {\n\t\t\tc.Create(context.Background(), r)\n\t\t}\n\n\t\tsecret := &corev1.Secret{}\n\t\tpoll.WaitOn(t, func(t poll.LogT) poll.Result {\n\t\t\terr := c.Get(context.Background(), client.ObjectKey{\n\t\t\t\tName:      \"example\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t}, secret)\n\n\t\t\tif err == nil {\n\t\t\t\treturn poll.Success()\n\t\t\t}\n\n\t\t\tif apierrors.IsNotFound(err) {\n\t\t\t\treturn poll.Continue(\"secret was not found\")\n\t\t\t}\n\n\t\t\treturn poll.Error(err)\n\t\t})\n\n\t\tcm := &corev1.ConfigMap{}\n\t\tc.Get(context.Background(), client.ObjectKey{\n\t\t\tName:      \"example\",\n\t\t\tNamespace: \"default\",\n\t\t}, cm)\n\n\t\tfmt.Printf(\"cm: %+v\\n\", *cm)\n\n\t\tassert.DeepEqual(t, secret, tc.expected,\n\t\t\tcmpopts.IgnoreFields(metav1.ObjectMeta{}, \"UID\", \"ResourceVersion\", \"CreationTimestamp\", \"ManagedFields\"),\n\t\t\tcmpopts.IgnoreFields(metav1.OwnerReference{}, \"UID\"),\n\t\t)\n\n\t\tfor _, r := range tc.resources {\n\t\t\tc.Delete(context.Background(), r)\n\t\t}\n\t\t// delete the created resource too, as there's no garbage collector\n\t\tc.Delete(context.Background(), tc.expected)\n\t}\n\n\ttestCases := []testCase{\n\t\t{\n\t\t\tname:        \"create secret\",\n\t\t\tlockboxName: \"example\",\n\t\t\tresources: []client.Object{\n\t\t\t\t&lockboxv1.Lockbox{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"example\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\t\"type\": \"lockbox\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\t\t\"helm.sh/hook\": \"pre-install\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tSpec: lockboxv1.LockboxSpec{\n\t\t\t\t\t\tSender:    []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},\n\t\t\t\t\t\tPeer:      []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},\n\t\t\t\t\t\tNamespace: []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},\n\t\t\t\t\t\tTemplate: lockboxv1.LockboxSecretTemplate{\n\t\t\t\t\t\t\tLockboxSecretTemplateMetadata: lockboxv1.LockboxSecretTemplateMetadata{\n\t\t\t\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\t\t\t\"type\": \"secret\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\t\t\t\t\"wave\": \"ignore\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\t\t\"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},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: &corev1.Secret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"example\",\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\"type\": \"secret\",\n\t\t\t\t\t},\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\t\"wave\": \"ignore\",\n\t\t\t\t\t},\n\t\t\t\t\tOwnerReferences: []metav1.OwnerReference{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAPIVersion:         \"lockbox.k8s.cloudflare.com/v1\",\n\t\t\t\t\t\t\tKind:               \"Lockbox\",\n\t\t\t\t\t\t\tName:               \"example\",\n\t\t\t\t\t\t\tController:         ptr.To(true),\n\t\t\t\t\t\t\tBlockOwnerDeletion: ptr.To(true),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tType: corev1.SecretTypeOpaque,\n\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\"test\": []byte(\"test\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:        \"avoids updating secrets owned by other controllers\",\n\t\t\tlockboxName: \"example\",\n\t\t\tresources: []client.Object{\n\t\t\t\t&corev1.Secret{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"example\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\tOwnerReferences: []metav1.OwnerReference{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tAPIVersion:         \"v1\",\n\t\t\t\t\t\t\t\tKind:               \"ConfigMap\",\n\t\t\t\t\t\t\t\tName:               \"example\",\n\t\t\t\t\t\t\t\tUID:                \"deadbeef\",\n\t\t\t\t\t\t\t\tController:         ptr.To(true),\n\t\t\t\t\t\t\t\tBlockOwnerDeletion: ptr.To(true),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\t\"test\":  []byte(\"test\"),\n\t\t\t\t\t\t\"test1\": []byte(\"test1\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&lockboxv1.Lockbox{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"example\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec: lockboxv1.LockboxSpec{\n\t\t\t\t\t\tSender:    []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},\n\t\t\t\t\t\tPeer:      []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},\n\t\t\t\t\t\tNamespace: []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},\n\t\t\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\t\t\"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},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: &corev1.Secret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"example\",\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\tOwnerReferences: []metav1.OwnerReference{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAPIVersion:         \"v1\",\n\t\t\t\t\t\t\tKind:               \"ConfigMap\",\n\t\t\t\t\t\t\tName:               \"example\",\n\t\t\t\t\t\t\tController:         ptr.To(true),\n\t\t\t\t\t\t\tBlockOwnerDeletion: ptr.To(true),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tType: corev1.SecretTypeOpaque,\n\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\"test\":  []byte(\"test\"),\n\t\t\t\t\t\"test1\": []byte(\"test1\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tsetup(t, tc)\n\t\t\trun(t, tc)\n\t\t})\n\t}\n\n}\n"
  },
  {
    "path": "pkg/lockbox-controller/secretreconciler_test.go",
    "content": "package controller_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\tlockboxv1 \"github.com/cloudflare/lockbox/pkg/apis/lockbox.k8s.cloudflare.com/v1\"\n\tcontroller \"github.com/cloudflare/lockbox/pkg/lockbox-controller\"\n\t\"github.com/kevinburke/nacl\"\n\t\"gotest.tools/v3/assert\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/utils/ptr\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tclientfake \"sigs.k8s.io/controller-runtime/pkg/client/fake\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n)\n\nfunc TestSecretReconciler(t *testing.T) {\n\ttype testCase struct {\n\t\tname        string\n\t\tlockboxName string\n\t\tresources   []client.Object\n\t\texpected    *corev1.Secret\n\t\texpectedErr string\n\t}\n\n\trun := func(t *testing.T, tc testCase) {\n\t\tscheme := runtime.NewScheme()\n\t\tassert.NilError(t, corev1.AddToScheme(scheme))\n\t\tassert.NilError(t, lockboxv1.AddToScheme(scheme))\n\n\t\tclient := clientfake.NewClientBuilder().\n\t\t\tWithObjects(tc.resources...).\n\t\t\tWithScheme(scheme).\n\t\t\tBuild()\n\n\t\tpubKey, priKey, err := loadKeypair(t, \"6a42b9fc2b011fb88c01741483e3bffe455bdab1ae35d0bb53a3c00d406d8836\", \"252173f975f0a0ddb198a7e5958c074203a0e9f44275e0b840f95d456c4acc2e\")\n\t\tassert.NilError(t, err)\n\n\t\tlsn := types.NamespacedName{Name: tc.lockboxName, Namespace: \"example\"}\n\t\tsr := controller.NewSecretReconciler(pubKey, priKey, controller.WithClient(client))\n\n\t\t_, err = reconcile.AsReconciler(client, sr).Reconcile(context.Background(), reconcile.Request{NamespacedName: lsn})\n\t\tif tc.expectedErr != \"\" {\n\t\t\tassert.ErrorContains(t, err, tc.expectedErr)\n\t\t} else {\n\t\t\tassert.NilError(t, err)\n\t\t}\n\n\t\tactual := &corev1.Secret{}\n\t\terr = client.Get(context.Background(), lsn, actual)\n\n\t\tif tc.expected == nil {\n\t\t\tassert.Assert(t, apierrors.IsNotFound(err))\n\t\t\treturn\n\t\t}\n\n\t\tassert.NilError(t, err)\n\t\tassert.DeepEqual(t, actual, tc.expected)\n\t}\n\n\ttestCases := []testCase{\n\t\t{\n\t\t\tname:        \"new lockbox\",\n\t\t\tlockboxName: \"example\",\n\t\t\tresources: []client.Object{\n\t\t\t\t&lockboxv1.Lockbox{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"example\",\n\t\t\t\t\t\tNamespace: \"example\",\n\t\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\t\"type\": \"lockbox\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\t\t\"helm.sh/hook\": \"pre-install\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tSpec: lockboxv1.LockboxSpec{\n\t\t\t\t\t\tSender:    []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},\n\t\t\t\t\t\tPeer:      []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},\n\t\t\t\t\t\tNamespace: []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},\n\t\t\t\t\t\tTemplate: lockboxv1.LockboxSecretTemplate{\n\t\t\t\t\t\t\tLockboxSecretTemplateMetadata: lockboxv1.LockboxSecretTemplateMetadata{\n\t\t\t\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\t\t\t\"type\": \"secret\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\t\t\t\t\"wave\": \"ignore\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tType: corev1.SecretTypeOpaque,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\t\t\"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},\n\t\t\t\t\t\t\t\"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},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: &corev1.Secret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"example\",\n\t\t\t\t\tNamespace: \"example\",\n\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\"type\": \"secret\",\n\t\t\t\t\t},\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\t\"wave\": \"ignore\",\n\t\t\t\t\t},\n\t\t\t\t\tResourceVersion: \"1\",\n\t\t\t\t\tOwnerReferences: []metav1.OwnerReference{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAPIVersion:         \"lockbox.k8s.cloudflare.com/v1\",\n\t\t\t\t\t\t\tKind:               \"Lockbox\",\n\t\t\t\t\t\t\tName:               \"example\",\n\t\t\t\t\t\t\tController:         ptr.To(true),\n\t\t\t\t\t\t\tBlockOwnerDeletion: ptr.To(true),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tType: corev1.SecretTypeOpaque,\n\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\"test\":  []byte(\"test\"),\n\t\t\t\t\t\"test1\": []byte(\"test1\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:        \"update lockbox secret\",\n\t\t\tlockboxName: \"example\",\n\t\t\tresources: []client.Object{\n\t\t\t\t&lockboxv1.Lockbox{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"example\",\n\t\t\t\t\t\tNamespace: \"example\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec: lockboxv1.LockboxSpec{\n\t\t\t\t\t\tSender:    []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},\n\t\t\t\t\t\tPeer:      []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},\n\t\t\t\t\t\tNamespace: []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},\n\t\t\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\t\t\"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},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tTemplate: lockboxv1.LockboxSecretTemplate{\n\t\t\t\t\t\t\tType: corev1.SecretTypeOpaque,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&corev1.Secret{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"example\",\n\t\t\t\t\t\tNamespace: \"example\",\n\t\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\t\"type\": \"secret\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\t\t\"wave\": \"ignore\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tResourceVersion: \"1\",\n\t\t\t\t\t\tOwnerReferences: []metav1.OwnerReference{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tAPIVersion:         \"lockbox.k8s.cloudflare.com/v1\",\n\t\t\t\t\t\t\t\tKind:               \"Lockbox\",\n\t\t\t\t\t\t\t\tName:               \"example\",\n\t\t\t\t\t\t\t\tController:         ptr.To(true),\n\t\t\t\t\t\t\t\tBlockOwnerDeletion: ptr.To(true),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tType: corev1.SecretTypeOpaque,\n\t\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\t\"test\":  []byte(\"test\"),\n\t\t\t\t\t\t\"test1\": []byte(\"test1\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: &corev1.Secret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:            \"example\",\n\t\t\t\t\tNamespace:       \"example\",\n\t\t\t\t\tResourceVersion: \"2\",\n\t\t\t\t\tOwnerReferences: []metav1.OwnerReference{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAPIVersion:         \"lockbox.k8s.cloudflare.com/v1\",\n\t\t\t\t\t\t\tKind:               \"Lockbox\",\n\t\t\t\t\t\t\tName:               \"example\",\n\t\t\t\t\t\t\tController:         ptr.To(true),\n\t\t\t\t\t\t\tBlockOwnerDeletion: ptr.To(true),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tType: corev1.SecretTypeOpaque,\n\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\"updated\": []byte(\"yep\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:        \"secret conflict\",\n\t\t\tlockboxName: \"example\",\n\t\t\tresources: []client.Object{\n\t\t\t\t&lockboxv1.Lockbox{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"example\",\n\t\t\t\t\t\tNamespace: \"example\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec: lockboxv1.LockboxSpec{\n\t\t\t\t\t\tSender:    []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},\n\t\t\t\t\t\tPeer:      []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},\n\t\t\t\t\t\tNamespace: []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},\n\t\t\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\t\t\"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},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&corev1.Secret{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:            \"example\",\n\t\t\t\t\t\tNamespace:       \"example\",\n\t\t\t\t\t\tResourceVersion: \"1\",\n\t\t\t\t\t\tOwnerReferences: []metav1.OwnerReference{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tAPIVersion:         \"bitnami.com/v1alpha1\",\n\t\t\t\t\t\t\t\tKind:               \"SealedSecret\",\n\t\t\t\t\t\t\t\tName:               \"example\",\n\t\t\t\t\t\t\t\tController:         ptr.To(true),\n\t\t\t\t\t\t\t\tBlockOwnerDeletion: ptr.To(true),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tType: corev1.SecretTypeOpaque,\n\t\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\t\"test\":  []byte(\"test\"),\n\t\t\t\t\t\t\"test1\": []byte(\"test1\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: &corev1.Secret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:            \"example\",\n\t\t\t\t\tNamespace:       \"example\",\n\t\t\t\t\tResourceVersion: \"1\",\n\t\t\t\t\tOwnerReferences: []metav1.OwnerReference{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAPIVersion:         \"bitnami.com/v1alpha1\",\n\t\t\t\t\t\t\tKind:               \"SealedSecret\",\n\t\t\t\t\t\t\tName:               \"example\",\n\t\t\t\t\t\t\tController:         ptr.To(true),\n\t\t\t\t\t\t\tBlockOwnerDeletion: ptr.To(true),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tType: corev1.SecretTypeOpaque,\n\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\"test\":  []byte(\"test\"),\n\t\t\t\t\t\"test1\": []byte(\"test1\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErr: \"already owned by another SealedSecret controller example\",\n\t\t},\n\t\t{\n\t\t\tname:        \"docker-registry secret\",\n\t\t\tlockboxName: \"example\",\n\t\t\tresources: []client.Object{\n\t\t\t\t&lockboxv1.Lockbox{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"example\",\n\t\t\t\t\t\tNamespace: \"example\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec: lockboxv1.LockboxSpec{\n\t\t\t\t\t\tSender:    []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},\n\t\t\t\t\t\tPeer:      []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},\n\t\t\t\t\t\tNamespace: []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},\n\t\t\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\t\t\".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},\n\t\t\t\t\t\t},\n\t\t\t\t\t\tTemplate: lockboxv1.LockboxSecretTemplate{\n\t\t\t\t\t\t\tType: corev1.SecretTypeDockerConfigJson,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: &corev1.Secret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:            \"example\",\n\t\t\t\t\tNamespace:       \"example\",\n\t\t\t\t\tResourceVersion: \"1\",\n\t\t\t\t\tOwnerReferences: []metav1.OwnerReference{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAPIVersion:         \"lockbox.k8s.cloudflare.com/v1\",\n\t\t\t\t\t\t\tKind:               \"Lockbox\",\n\t\t\t\t\t\t\tName:               \"example\",\n\t\t\t\t\t\t\tController:         ptr.To(true),\n\t\t\t\t\t\t\tBlockOwnerDeletion: ptr.To(true),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tType: corev1.SecretTypeDockerConfigJson,\n\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\".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},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trun(t, tc)\n\t\t})\n\t}\n}\n\nfunc loadKeypair(t *testing.T, pub, pri string) (pubKey, priKey nacl.Key, err error) {\n\tt.Helper()\n\n\tpubKey, err = nacl.Load(pub)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tpriKey, err = nacl.Load(pri)\n\treturn\n}\n"
  },
  {
    "path": "pkg/lockbox-server/serve.go",
    "content": "package server\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/kevinburke/nacl\"\n)\n\n// PublicKey creates an HTTP handler that responses with the specified public key\n// as binary data.\nfunc PublicKey(pubKey nacl.Key) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"application/octet-stream\")\n\t\t_, _ = w.Write(pubKey[:])\n\t})\n}\n"
  },
  {
    "path": "pkg/statemetrics/collector.go",
    "content": "package statemetrics\n\nimport (\n\t\"sync\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"k8s.io/apimachinery/pkg/types\"\n)\n\n// Kubernetes is a metric tracking a numerical value that can arbitrary go up and down.\ntype Kubernetes interface {\n\tprometheus.Metric\n\tprometheus.Collector\n\n\t// Set sets the tracked number to an arbitrary value.\n\tSet(float64)\n}\n\n// kubernetes implements Kubernetes.\ntype kubernetes struct {\n\tprometheus.Metric\n\tvalues []string\n\tdesc   *prometheus.Desc\n}\n\n// Set creates a new constant metric for this numerical value\nfunc (k *kubernetes) Set(val float64) {\n\tk.Metric = prometheus.MustNewConstMetric(k.desc, prometheus.GaugeValue, val, k.values...)\n}\n\n// Describe implements Collector\nfunc (k *kubernetes) Describe(ch chan<- *prometheus.Desc) {\n\tch <- k.desc\n}\n\n// Collect implements Collector\nfunc (k *kubernetes) Collect(ch chan<- prometheus.Metric) {\n\tch <- k.Metric\n}\n\n// KubernetesVec is a Collector that bundles a set of Kubernetes metrics that all share the same Desc,\n// but have different values for their variable labels. This is used if you want to count the same\n// thing partitioned by various dimensions (e.g., namespaces, types). Create instances with\n// NewKubernetesVec.\ntype KubernetesVec struct {\n\tdesc    *prometheus.Desc\n\tmetrics map[types.UID]Kubernetes\n\tmu      sync.Mutex\n}\n\n// KubernetesOpts is an alias for Opts.\ntype KubernetesOpts prometheus.Opts\n\n// NewKubernetesVec creates a new KubernetesVec based on the provided KubernetesOpts and partitioned\n// by the given label names.\nfunc NewKubernetesVec(opts KubernetesOpts, labelNames []string) *KubernetesVec {\n\tdesc := prometheus.NewDesc(\n\t\tprometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),\n\t\topts.Help,\n\t\tlabelNames,\n\t\topts.ConstLabels,\n\t)\n\n\treturn &KubernetesVec{\n\t\tdesc:    desc,\n\t\tmetrics: make(map[types.UID]Kubernetes),\n\t}\n}\n\n// WithLabelValues returns the Kubernetes metric for the given slice of label values (in the same order\n// as the variable labels).\n//\n// Consecutive calls for the same uid replace earlier metrics.\nfunc (v *KubernetesVec) WithLabelValues(uid types.UID, lvs ...string) Kubernetes {\n\tk := &kubernetes{\n\t\tvalues: lvs,\n\t\tdesc:   v.desc,\n\t}\n\tk.Set(0)\n\n\tv.mu.Lock()\n\tv.metrics[uid] = k\n\tv.mu.Unlock()\n\n\treturn k\n}\n\n// Delete deletes the metric stored for this uid.\nfunc (v *KubernetesVec) Delete(uid types.UID) {\n\tv.mu.Lock()\n\tdefer v.mu.Unlock()\n\n\tdelete(v.metrics, uid)\n}\n\n// Describe implements Collector.\nfunc (v *KubernetesVec) Describe(ch chan<- *prometheus.Desc) {\n\tch <- v.desc\n}\n\n// Collect implements Collector.\nfunc (v *KubernetesVec) Collect(ch chan<- prometheus.Metric) {\n\tv.mu.Lock()\n\tdefer v.mu.Unlock()\n\n\tfor _, metric := range v.metrics {\n\t\tch <- metric\n\t}\n}\n\n// LabelsVec is a Collector that bundles a set of unchecked Kubernetes metrics that all share the\n// same Desc, but have different labels. This is used if you want to count the same thing\n// partitioned by unbounded dimensions (e.g., Kubernetes labels). Create instances with\n// NewLabelsVec.\ntype LabelsVec struct {\n\topts    KubernetesOpts\n\tmetrics map[types.UID]Kubernetes\n\tmu      sync.Mutex\n}\n\n// NewLabelsVec creates a new LabelsVec based on the provided KubernetesOpts.\nfunc NewLabelsVec(opts KubernetesOpts) *LabelsVec {\n\treturn &LabelsVec{\n\t\topts:    opts,\n\t\tmetrics: make(map[types.UID]Kubernetes),\n\t}\n}\n\n// With returns the Kubernetes metric for the given Labels map.\n//\n// Consecutive calls with the same uid replace earlier metrics.\nfunc (v *LabelsVec) With(uid types.UID, l prometheus.Labels) Kubernetes {\n\tlabels := make([]string, 0, len(l))\n\tvalues := make([]string, 0, len(l))\n\n\tfor label, value := range l {\n\t\tlabels = append(labels, label)\n\t\tvalues = append(values, value)\n\t}\n\n\tdesc := prometheus.NewDesc(\n\t\tprometheus.BuildFQName(v.opts.Namespace, v.opts.Subsystem, v.opts.Name),\n\t\tv.opts.Help,\n\t\tlabels,\n\t\tv.opts.ConstLabels,\n\t)\n\n\tk := &kubernetes{\n\t\tvalues: values,\n\t\tdesc:   desc,\n\t}\n\tk.Set(0)\n\n\tv.mu.Lock()\n\tv.metrics[uid] = k\n\tv.mu.Unlock()\n\n\treturn k\n}\n\n// Delete deletes the metric stored for this uid.\nfunc (v *LabelsVec) Delete(uid types.UID) {\n\tv.mu.Lock()\n\tdefer v.mu.Unlock()\n\n\tdelete(v.metrics, uid)\n}\n\n// Describe implements Collector. No Desc are sent on the channel to make this\n// an unchecked collector.\nfunc (v *LabelsVec) Describe(chan<- *prometheus.Desc) {}\n\n// Collect implements Collector.\nfunc (v *LabelsVec) Collect(ch chan<- prometheus.Metric) {\n\tv.mu.Lock()\n\tdefer v.mu.Unlock()\n\n\tfor _, metric := range v.metrics {\n\t\tch <- metric\n\t}\n}\n"
  },
  {
    "path": "pkg/statemetrics/handler.go",
    "content": "package statemetrics\n\nimport (\n\t\"context\"\n\t\"encoding/hex\"\n\n\tlockboxv1 \"github.com/cloudflare/lockbox/pkg/apis/lockbox.k8s.cloudflare.com/v1\"\n\t\"k8s.io/client-go/util/workqueue\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/event\"\n\t\"sigs.k8s.io/controller-runtime/pkg/handler\"\n)\n\n// StateMetricProxy updates state metrics by intercepting events as they are passed to event handlers.\ntype StateMetricProxy struct {\n\tenqueuer handler.EventHandler\n\n\tinfo            *KubernetesVec\n\tcreated         *KubernetesVec\n\tresourceVersion *KubernetesVec\n\tlbType          *KubernetesVec\n\tpeerKey         *KubernetesVec\n\tlabels          *LabelsVec\n}\n\n// NewStateMetricProxy returns a StateMetricsProxy. All metrics must be non-nil.\nfunc NewStateMetricProxy(enqueuer handler.EventHandler, info, created, resourceVersion, lbType, peerKey *KubernetesVec, labels *LabelsVec) *StateMetricProxy {\n\treturn &StateMetricProxy{\n\t\tenqueuer:        enqueuer,\n\t\tinfo:            info,\n\t\tcreated:         created,\n\t\tresourceVersion: resourceVersion,\n\t\tlbType:          lbType,\n\t\tpeerKey:         peerKey,\n\t\tlabels:          labels,\n\t}\n}\n\n// Create implements EventHandler.\nfunc (s *StateMetricProxy) Create(ctx context.Context, evt event.CreateEvent, q workqueue.RateLimitingInterface) {\n\ts.updateWith(evt.Object)\n\n\tif s.enqueuer != nil {\n\t\ts.enqueuer.Create(ctx, evt, q)\n\t}\n}\n\n// Update implements EventHandler.\nfunc (s *StateMetricProxy) Update(ctx context.Context, evt event.UpdateEvent, q workqueue.RateLimitingInterface) {\n\ts.updateWith(evt.ObjectNew)\n\n\tif s.enqueuer != nil {\n\t\ts.enqueuer.Update(ctx, evt, q)\n\t}\n}\n\n// Delete implements EventHandler.\nfunc (s *StateMetricProxy) Delete(ctx context.Context, evt event.DeleteEvent, q workqueue.RateLimitingInterface) {\n\tuid := evt.Object.GetUID()\n\n\ts.info.Delete(uid)\n\ts.created.Delete(uid)\n\ts.resourceVersion.Delete(uid)\n\ts.lbType.Delete(uid)\n\ts.peerKey.Delete(uid)\n\ts.labels.Delete(uid)\n\n\tif s.enqueuer != nil {\n\t\ts.enqueuer.Delete(ctx, evt, q)\n\t}\n}\n\n// Generic implements EventHandler.\nfunc (s *StateMetricProxy) Generic(ctx context.Context, evt event.GenericEvent, q workqueue.RateLimitingInterface) {\n\tif s.enqueuer != nil {\n\t\ts.enqueuer.Generic(ctx, evt, q)\n\t}\n}\n\n// updateWith updates the metrics for Create and Update handles.\nfunc (s *StateMetricProxy) updateWith(obj client.Object) {\n\tnamespace := obj.GetNamespace()\n\tlockbox := obj.GetName()\n\tuid := obj.GetUID()\n\n\ts.info.WithLabelValues(uid, namespace, lockbox).Set(1)\n\tcreationTime := obj.GetCreationTimestamp()\n\tif !creationTime.IsZero() {\n\t\ts.created.WithLabelValues(uid, namespace, lockbox).Set(float64(creationTime.Unix()))\n\t}\n\ts.resourceVersion.WithLabelValues(uid, namespace, lockbox, obj.GetResourceVersion()).Set(1)\n\n\tif lb, ok := obj.(*lockboxv1.Lockbox); ok {\n\t\ts.lbType.WithLabelValues(uid, namespace, lockbox, string(lb.Spec.Template.Type)).Set(1)\n\t\ts.peerKey.WithLabelValues(uid, namespace, lockbox, hex.EncodeToString(lb.Spec.Peer)).Set(1)\n\t}\n\n\tpromLabels := kubernetesLabelsToPrometheusLabels(obj.GetLabels())\n\tpromLabels[\"namespace\"] = namespace\n\tpromLabels[\"lockbox\"] = lockbox\n\n\ts.labels.With(uid, promLabels).Set(1)\n}\n"
  },
  {
    "path": "pkg/statemetrics/handler_test.go",
    "content": "package statemetrics\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\tlockboxv1 \"github.com/cloudflare/lockbox/pkg/apis/lockbox.k8s.cloudflare.com/v1\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/testutil\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"sigs.k8s.io/controller-runtime/pkg/event\"\n)\n\nfunc TestStateMetricsProxy_Create(t *testing.T) {\n\tlb := &lockboxv1.Lockbox{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tNamespace:         \"fizz\",\n\t\t\tName:              \"buzz\",\n\t\t\tUID:               \"foobar\",\n\t\t\tResourceVersion:   \"9001\",\n\t\t\tCreationTimestamp: metav1.Date(2001, time.September, 9, 1, 46, 40, 0, time.UTC),\n\t\t\tLabels: map[string]string{\n\t\t\t\t\"testing\": \"true\",\n\t\t\t},\n\t\t},\n\t\tSpec: lockboxv1.LockboxSpec{\n\t\t\tPeer: []byte{0xDE, 0xAD, 0xBE, 0xEF},\n\t\t\tTemplate: lockboxv1.LockboxSecretTemplate{\n\t\t\t\tType: \"golang.org/testing\",\n\t\t\t},\n\t\t},\n\t}\n\tinfo, created, resourceVersion, lbType, peerKey, labels := createMetricVectors(t)\n\n\treg := prometheus.NewPedanticRegistry()\n\treg.MustRegister(info, created, resourceVersion, lbType, peerKey, labels)\n\n\tevt := event.CreateEvent{Object: lb}\n\n\thandler := NewStateMetricProxy(nil, info, created, resourceVersion, lbType, peerKey, labels)\n\thandler.Create(context.Background(), evt, nil)\n\n\texpected := strings.NewReader(`\n# HELP kube_lockbox_info Information about Lockbox\n# TYPE kube_lockbox_info gauge\nkube_lockbox_info{lockbox=\"buzz\",namespace=\"fizz\"} 1\n# HELP kube_lockbox_created Unix creation timestamp\n# TYPE kube_lockbox_created gauge\nkube_lockbox_created{lockbox=\"buzz\",namespace=\"fizz\"} 1e9\n# HELP kube_lockbox_resource_version Resource version representing a specific version of a Lockbox\n# TYPE kube_lockbox_resource_version gauge\nkube_lockbox_resource_version{lockbox=\"buzz\",namespace=\"fizz\",resource_version=\"9001\"} 1\n# HELP kube_lockbox_type Lockbox secret type\n# TYPE kube_lockbox_type gauge\nkube_lockbox_type{lockbox=\"buzz\",namespace=\"fizz\",type=\"golang.org/testing\"} 1\n# HELP kube_lockbox_peer Lockbox peer key\n# TYPE kube_lockbox_peer gauge\nkube_lockbox_peer{lockbox=\"buzz\",namespace=\"fizz\",peer=\"deadbeef\"} 1\n# HELP kube_lockbox_labels Kubernetes labels converted to Prometheus labels\n# TYPE kube_lockbox_labels gauge\nkube_lockbox_labels{label_testing=\"true\",lockbox=\"buzz\",namespace=\"fizz\"} 1\n`)\n\n\tif err := testutil.GatherAndCompare(reg, expected); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestStateMetricsProxy_Update(t *testing.T) {\n\told := &lockboxv1.Lockbox{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tNamespace:         \"fizz\",\n\t\t\tName:              \"buzz\",\n\t\t\tUID:               \"foobar\",\n\t\t\tResourceVersion:   \"8999\",\n\t\t\tCreationTimestamp: metav1.Now(),\n\t\t\tLabels: map[string]string{\n\t\t\t\t\"testing\": \"false\",\n\t\t\t},\n\t\t},\n\t\tSpec: lockboxv1.LockboxSpec{\n\t\t\tPeer: []byte{0x00},\n\t\t\tTemplate: lockboxv1.LockboxSecretTemplate{\n\t\t\t\tType: \"example.org/old\",\n\t\t\t},\n\t\t},\n\t}\n\tlb := &lockboxv1.Lockbox{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tNamespace:         \"fizz\",\n\t\t\tName:              \"buzz\",\n\t\t\tUID:               \"foobar\",\n\t\t\tResourceVersion:   \"9001\",\n\t\t\tCreationTimestamp: metav1.Date(2001, time.September, 9, 1, 46, 40, 0, time.UTC),\n\t\t\tLabels: map[string]string{\n\t\t\t\t\"testing\": \"true\",\n\t\t\t},\n\t\t},\n\t\tSpec: lockboxv1.LockboxSpec{\n\t\t\tPeer: []byte{0xDE, 0xAD, 0xBE, 0xEF},\n\t\t\tTemplate: lockboxv1.LockboxSecretTemplate{\n\t\t\t\tType: \"golang.org/testing\",\n\t\t\t},\n\t\t},\n\t}\n\tinfo, created, resourceVersion, lbType, peerKey, labels := createMetricVectors(t)\n\n\treg := prometheus.NewPedanticRegistry()\n\treg.MustRegister(info, created, resourceVersion, lbType, peerKey, labels)\n\n\tcreate := event.CreateEvent{Object: old}\n\tupd := event.UpdateEvent{\n\t\tObjectOld: old,\n\t\tObjectNew: lb,\n\t}\n\n\thandler := NewStateMetricProxy(nil, info, created, resourceVersion, lbType, peerKey, labels)\n\thandler.Create(context.Background(), create, nil)\n\thandler.Update(context.Background(), upd, nil)\n\n\texpected := strings.NewReader(`\n# HELP kube_lockbox_info Information about Lockbox\n# TYPE kube_lockbox_info gauge\nkube_lockbox_info{lockbox=\"buzz\",namespace=\"fizz\"} 1\n# HELP kube_lockbox_created Unix creation timestamp\n# TYPE kube_lockbox_created gauge\nkube_lockbox_created{lockbox=\"buzz\",namespace=\"fizz\"} 1e9\n# HELP kube_lockbox_resource_version Resource version representing a specific version of a Lockbox\n# TYPE kube_lockbox_resource_version gauge\nkube_lockbox_resource_version{lockbox=\"buzz\",namespace=\"fizz\",resource_version=\"9001\"} 1\n# HELP kube_lockbox_type Lockbox secret type\n# TYPE kube_lockbox_type gauge\nkube_lockbox_type{lockbox=\"buzz\",namespace=\"fizz\",type=\"golang.org/testing\"} 1\n# HELP kube_lockbox_peer Lockbox peer key\n# TYPE kube_lockbox_peer gauge\nkube_lockbox_peer{lockbox=\"buzz\",namespace=\"fizz\",peer=\"deadbeef\"} 1\n# HELP kube_lockbox_labels Kubernetes labels converted to Prometheus labels\n# TYPE kube_lockbox_labels gauge\nkube_lockbox_labels{label_testing=\"true\",lockbox=\"buzz\",namespace=\"fizz\"} 1\n`)\n\n\tif err := testutil.GatherAndCompare(reg, expected); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestStateMetricsProxy_Delete(t *testing.T) {\n\tlb := &lockboxv1.Lockbox{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tNamespace:         \"fizz\",\n\t\t\tName:              \"buzz\",\n\t\t\tUID:               \"foobar\",\n\t\t\tResourceVersion:   \"9001\",\n\t\t\tCreationTimestamp: metav1.Date(2001, time.September, 9, 1, 46, 40, 0, time.UTC),\n\t\t\tLabels: map[string]string{\n\t\t\t\t\"testing\": \"true\",\n\t\t\t},\n\t\t},\n\t\tSpec: lockboxv1.LockboxSpec{\n\t\t\tPeer: []byte{0xDE, 0xAD, 0xBE, 0xEF},\n\t\t\tTemplate: lockboxv1.LockboxSecretTemplate{\n\t\t\t\tType: \"golang.org/testing\",\n\t\t\t},\n\t\t},\n\t}\n\tinfo, created, resourceVersion, lbType, peerKey, labels := createMetricVectors(t)\n\n\treg := prometheus.NewPedanticRegistry()\n\treg.MustRegister(info, created, resourceVersion, lbType, peerKey, labels)\n\n\tcreate := event.CreateEvent{Object: lb}\n\tdeleted := event.DeleteEvent{\n\t\tObject:             lb,\n\t\tDeleteStateUnknown: false,\n\t}\n\n\thandler := NewStateMetricProxy(nil, info, created, resourceVersion, lbType, peerKey, labels)\n\thandler.Create(context.Background(), create, nil)\n\thandler.Delete(context.Background(), deleted, nil)\n\n\texpected := &strings.Reader{}\n\n\tif err := testutil.GatherAndCompare(reg, expected); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc createMetricVectors(t *testing.T) (info, created, resourceVersion, lbType, peerKey *KubernetesVec, labels *LabelsVec) {\n\tinfo = NewKubernetesVec(KubernetesOpts{\n\t\tName: \"kube_lockbox_info\",\n\t\tHelp: \"Information about Lockbox\",\n\t}, []string{\"namespace\", \"lockbox\"})\n\tcreated = NewKubernetesVec(KubernetesOpts{\n\t\tName: \"kube_lockbox_created\",\n\t\tHelp: \"Unix creation timestamp\",\n\t}, []string{\"namespace\", \"lockbox\"})\n\tresourceVersion = NewKubernetesVec(KubernetesOpts{\n\t\tName: \"kube_lockbox_resource_version\",\n\t\tHelp: \"Resource version representing a specific version of a Lockbox\",\n\t}, []string{\"namespace\", \"lockbox\", \"resource_version\"})\n\tlbType = NewKubernetesVec(KubernetesOpts{\n\t\tName: \"kube_lockbox_type\",\n\t\tHelp: \"Lockbox secret type\",\n\t}, []string{\"namespace\", \"lockbox\", \"type\"})\n\tpeerKey = NewKubernetesVec(KubernetesOpts{\n\t\tName: \"kube_lockbox_peer\",\n\t\tHelp: \"Lockbox peer key\",\n\t}, []string{\"namespace\", \"lockbox\", \"peer\"})\n\tlabels = NewLabelsVec(KubernetesOpts{\n\t\tName: \"kube_lockbox_labels\",\n\t\tHelp: \"Kubernetes labels converted to Prometheus labels\",\n\t})\n\n\treturn\n}\n"
  },
  {
    "path": "pkg/statemetrics/labels.go",
    "content": "package statemetrics\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n)\n\nvar invalidLabelCharRE = regexp.MustCompile(`[^a-zA-Z0-9_]`)\n\n// sanitizeLabel replaces non-alphanumeric characters with underscores.\nfunc sanitizeLabel(l string) string {\n\treturn invalidLabelCharRE.ReplaceAllString(l, \"_\")\n}\n\n// kubernetesLabelsToPrometheusLabels generates Prometheus-safe labels from\n// the resource's Kubernetes labels.\nfunc kubernetesLabelsToPrometheusLabels(labels map[string]string) prometheus.Labels {\n\tpromLabels := map[string]string{}\n\tfor l, v := range labels {\n\t\tpromLabels[\"label_\"+sanitizeLabel(l)] = v\n\t}\n\n\treturn promLabels\n}\n"
  },
  {
    "path": "pkg/statemetrics/labels_test.go",
    "content": "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 reservedLabelPrefix = \"__\"\n\nfunc TestLabelsTransformation(t *testing.T) {\n\tf := func(labels map[string]string) bool {\n\t\tnewLabels := kubernetesLabelsToPrometheusLabels(labels)\n\n\t\tfor k := range newLabels {\n\t\t\tif !checkLabelName(k) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\n\t\treturn true\n\t}\n\n\tif err := quick.Check(f, nil); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc checkLabelName(l string) bool {\n\treturn model.LabelName(l).IsValid() && !strings.HasPrefix(l, reservedLabelPrefix)\n}\n"
  },
  {
    "path": "pkg/util/conditions/conditions.go",
    "content": "// Adapted from https://github.com/kubernetes-sigs/cluster-api/tree/v0.3.10/util/conditions\n//\n// Copyright 2020 The Kubernetes Authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package conditions provides functions for setting status conditions on Lockbox resources\npackage conditions\n\nimport (\n\t\"sort\"\n\t\"time\"\n\n\tlockboxv1 \"github.com/cloudflare/lockbox/pkg/apis/lockbox.k8s.cloudflare.com/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\ntype Getter interface {\n\tGetConditions() []lockboxv1.Condition\n}\n\ntype Setter interface {\n\tGetter\n\tSetConditions([]lockboxv1.Condition)\n}\n\nfunc FalseCondition(t lockboxv1.ConditionType, reason string, severity lockboxv1.ConditionSeverity, message string) *lockboxv1.Condition {\n\treturn &lockboxv1.Condition{\n\t\tType:     t,\n\t\tStatus:   \"False\",\n\t\tReason:   reason,\n\t\tSeverity: severity,\n\t\tMessage:  message,\n\t}\n}\n\nfunc TrueCondition(t lockboxv1.ConditionType) *lockboxv1.Condition {\n\treturn &lockboxv1.Condition{\n\t\tType:   t,\n\t\tStatus: \"True\",\n\t}\n}\n\nfunc UnknownCondition(t lockboxv1.ConditionType, reason string, message string) *lockboxv1.Condition {\n\treturn &lockboxv1.Condition{\n\t\tType:    t,\n\t\tStatus:  \"Unknown\",\n\t\tReason:  reason,\n\t\tMessage: message,\n\t}\n}\n\nfunc Get(from Getter, t lockboxv1.ConditionType) *lockboxv1.Condition {\n\tconditions := from.GetConditions()\n\tif conditions == nil {\n\t\treturn nil\n\t}\n\n\tfor _, condition := range conditions {\n\t\tif condition.Type == t {\n\t\t\treturn &condition\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc Set(to Setter, condition *lockboxv1.Condition) {\n\tif to == nil || condition == nil {\n\t\treturn\n\t}\n\n\tconditions := to.GetConditions()\n\texists := false\n\tfor i := range conditions {\n\t\texistingCondition := conditions[i]\n\t\tif existingCondition.Type == condition.Type {\n\t\t\texists = true\n\t\t\tif !hasSameState(&existingCondition, condition) {\n\t\t\t\tcondition.LastTransitionTime = metav1.NewTime(time.Now().UTC().Truncate(time.Second))\n\t\t\t\tconditions[i] = *condition\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcondition.LastTransitionTime = existingCondition.LastTransitionTime\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !exists {\n\t\tif condition.LastTransitionTime.IsZero() {\n\t\t\tcondition.LastTransitionTime = metav1.NewTime(time.Now().UTC().Truncate(time.Second))\n\t\t}\n\t\tconditions = append(conditions, *condition)\n\t}\n\n\tsort.Slice(conditions, func(i, j int) bool {\n\t\treturn lexicographicLess(&conditions[i], &conditions[j])\n\t})\n\n\tto.SetConditions(conditions)\n}\n\nfunc lexicographicLess(i, j *lockboxv1.Condition) bool {\n\treturn (i.Type == \"Ready\" || i.Type < j.Type) && j.Type != \"Ready\"\n}\n\nfunc hasSameState(i, j *lockboxv1.Condition) bool {\n\treturn i.Type == j.Type &&\n\t\ti.Status == j.Status &&\n\t\ti.Reason == j.Reason &&\n\t\ti.Severity == j.Severity &&\n\t\ti.Message == j.Message\n}\n"
  },
  {
    "path": "tools/tools.go",
    "content": "//go:build tools\n// +build tools\n\npackage tools\n\nimport (\n\t_ \"sigs.k8s.io/controller-tools/cmd/controller-gen\"\n)\n"
  }
]