[
  {
    "path": ".gitattributes",
    "content": "vendor_jsonnet/ linguist-generated=true\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "# These owners will be the default owners for everything in\n# the repo. Unless a later match takes precedence,\n# @alvneiayu @agarcia-oss @alemorcuq will be requested for\n# review when someone opens a pull request.\n* @alvneiayu @agarcia-oss\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: triage\nassignees: ''\n\n---\n\n<!--\n Before you open the bug report please review the following FAQ:\n\n - [Sealed Secrets FAQ](https://github.com/bitnami-labs/sealed-secrets#faq)\n -->\n\n**Which component**:\nThe name (and version) of the affected component (controller or kubeseal)\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n\n1. Go to '...'\n2. Run the command '....'\n3. Wait for '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Version of Kubernetes**:\n\n- Output of `kubectl version`:\n\n```\n(paste your output here)\n```\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: triage\nassignees: ''\n\n---\n\n**Which component**:\nThe name (and version) of the affected component (controller or kubeseal)\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--\n Before you open the request please review the following guidelines and tips to help it be more easily integrated:\n\n - Describe the scope of your change - i.e. what the change does.\n - Describe any known limitations with your change.\n - Please run any tests or examples that can exercise your modified code.\n\n Thank you for contributing! We will try to test and integrate the change as soon as we can, but be aware we have many GitHub repositories to manage and can't immediately respond to every request. There is no need to bump or check in on a pull request (it will clutter the discussion of the request).\n\n Also don't be worried if the request is closed or not integrated sometimes the priorities of Bitnami might not match the priorities of the pull request. Don't fret, the open source community thrives on forks and GitHub makes it easy to keep your changes in a forked repo.\n -->\n\n**Description of the change**\n\n<!-- Describe the scope of your change - i.e. what the change does. -->\n\n**Benefits**\n\n<!-- What benefits will be realized by the code change? -->\n\n**Possible drawbacks**\n\n<!-- Describe any known limitations with your change -->\n\n**Applicable issues**\n\n<!-- Enter any applicable Issues here (You can reference an issue using #) -->\n- fixes #\n\n**Additional information**\n\n<!-- If there's anything else that's important and relevant to your pull\nrequest, mention that information here.-->\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\n\nversion: 2\nupdates:\n  - package-ecosystem: \"gomod\" # See documentation for possible values\n    directory: \"/\" # Location of package manifests\n    schedule:\n      interval: \"weekly\"\n\n  # Enable version updates for Docker\n  - package-ecosystem: \"docker\"\n    directory: \"/docker\"\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n\nenv:\n  controller_registry: docker.io\n  controller_repository: bitnami/sealed-secrets-controller\n  controller_tag: latest\n\njobs:\n  load-versions:\n    name: Load versions.env\n    runs-on: ubuntu-latest\n    steps:\n      - name: checkout repo\n        uses: actions/checkout@v6.0.2\n      - id: load-versions\n        run: |\n          source $GITHUB_WORKSPACE/versions.env\n          # env vars\n          echo \"GO_VERSION=$GO_VERSION\" >> $GITHUB_ENV\n          echo \"GO_VERSION_LIST=$GO_VERSION_LIST\" >> $GITHUB_ENV\n          # outputs\n          echo \"go_version=${GO_VERSION}\" >> $GITHUB_OUTPUT\n          echo \"go_version_list=${GO_VERSION_LIST}\" >> $GITHUB_OUTPUT\n    outputs:\n      go_version: ${{ steps.load-versions.outputs.go_version }}\n      go_version_list: ${{ steps.load-versions.outputs.go_version_list }}\n  linter:\n    needs: load-versions\n    name: Run linters\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        go: ${{ fromJSON(needs.load-versions.outputs.go_version_list) }}\n        os: [ubuntu-latest]\n        golangci-lint: [\"1.64.8\"]\n        gosec: [\"2.22.2\"]\n    steps:\n    - name: Set up Go 1.x\n      uses: actions/setup-go@v6.3.0\n      with:\n        go-version: ${{ matrix.go }}\n      id: go\n\n    - name: Check out code into the Go module directory\n      uses: actions/checkout@v6.0.2\n\n    - name: Install dependencies\n      run: |\n        go install github.com/golangci/golangci-lint/cmd/golangci-lint@v${{ matrix.golangci-lint }}\n        go install github.com/securego/gosec/v2/cmd/gosec@v${{ matrix.gosec }}\n\n    - name: Run linter\n      run: make lint\n\n    - name: Run gosec\n      run: make lint-gosec\n\n  test:\n    needs: load-versions\n    name: Build\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        go: ${{ fromJSON(needs.load-versions.outputs.go_version_list) }}\n        os: [macos-latest, windows-latest, ubuntu-latest]\n        gotestsum: [\"1.8.1\"]\n    steps:\n\n    - name: Set up Go 1.x\n      uses: actions/setup-go@v6.3.0\n      with:\n        go-version: ${{ matrix.go }}\n      id: go\n\n    - name: Check out code into the Go module directory\n      uses: actions/checkout@v6.0.2\n\n    - name: Install dependencies\n      run: |\n        go install gotest.tools/gotestsum@v${{ matrix.gotestsum }}\n\n    - name: Test\n      run: make GO_FLAGS=\"--junitfile report.xml --format testname\" test\n\n    - name: Test Summary\n      uses: test-summary/action@v2\n      with:\n        paths: |\n          report.xml\n\n  container:\n    needs: load-versions\n    name: Build Container\n    runs-on: ubuntu-latest\n    steps:\n    - name: \"Set environmental variables\"\n      run: |\n        echo \"CONTROLLER_IMAGE=$controller_registry/$controller_repository:$controller_tag\" >> $GITHUB_ENV\n\n    - name: Check out code\n      uses: actions/checkout@v6.0.2\n\n    - name: Install Cosign\n      uses: sigstore/cosign-installer@v3.4.0\n      with:\n        cosign-release: v2.2.3\n\n    - name: Distroless verify\n      run: |\n        diff <(grep FROM docker/kubeseal.Dockerfile | awk '{print $2}') \\\n             <(grep FROM docker/controller.Dockerfile | awk '{print $2}')\n        cosign verify \"$(grep FROM docker/controller.Dockerfile | awk '{print $2}')\" --certificate-oidc-issuer https://accounts.google.com  --certificate-identity keyless@distroless.iam.gserviceaccount.com\n\n    - name: Setup kubecfg\n      run: |\n        mkdir -p ~/bin\n        curl -sLf https://github.com/kubecfg/kubecfg/releases/download/v0.26.0/kubecfg_Linux_X64 >~/bin/kubecfg\n        chmod +x ~/bin/kubecfg\n\n    - name: Set up Go 1.x\n      uses: actions/setup-go@v6.3.0\n      with:\n        go-version: ${{ needs.load-versions.outputs.go_version }}\n      id: go\n\n    - name: Docker build\n      run: |\n        export PATH=~/bin:$PATH\n        make CONTROLLER_IMAGE=$CONTROLLER_IMAGE IMAGE_PULL_POLICY=Never controller.yaml\n        make CONTROLLER_IMAGE=$CONTROLLER_IMAGE controller.image.linux-amd64\n        docker tag $CONTROLLER_IMAGE-linux-amd64 $CONTROLLER_IMAGE\n        docker save $CONTROLLER_IMAGE -o /tmp/controller-image.tar\n\n    - name: Upload manifest artifact\n      uses: actions/upload-artifact@v6.0.0\n      with:\n        name: controller-manifest\n        path: controller.yaml\n\n    - name: Upload container image artifact\n      uses: actions/upload-artifact@v6.0.0\n      with:\n        name: controller-image\n        path: /tmp/controller-image.tar\n\n  integration-yaml:\n    needs: [ load-versions, container ]\n    name: Integration (controller.yaml)\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        k8s: [\"1.32.12\",\"1.33.8\",\"1.34.4\",\"1.35.1\"]\n    env:\n      MINIKUBE_WANTUPDATENOTIFICATION: \"false\"\n      MINIKUBE_WANTREPORTERRORPROMPT: \"false\"\n      CHANGE_MINIKUBE_NONE_USER: \"true\"\n    steps:\n    - name: \"Set environmental variables\"\n      run: |\n        echo \"CONTROLLER_IMAGE=$controller_registry/$controller_repository:$controller_tag\" >> $GITHUB_ENV\n\n    - name: Set up Go 1.x\n      uses: actions/setup-go@v6.3.0\n      with:\n        go-version: ${{ needs.load-versions.outputs.go_version }}\n      id: go\n\n    - name: Set up Ginkgo\n      run: |\n        go install github.com/onsi/ginkgo/ginkgo@v1.16.4\n\n    - name: Check out code into the Go module directory\n      uses: actions/checkout@v6.0.2\n\n    - uses: medyagh/setup-minikube@v0.0.21\n      with:\n        minikube-version: 1.38.0\n        kubernetes-version: ${{ matrix.k8s }}\n\n    # need to delete old state of the cluster, see:\n    # https://github.com/kubernetes/minikube/issues/8765\n    - name: K8s setup\n      run: |\n        minikube delete\n        minikube config set kubernetes-version v${{ matrix.k8s }}\n        minikube start --vm-driver=docker\n        minikube update-context\n        kubectl cluster-info\n\n    - name: Download manifest artifact\n      uses: actions/download-artifact@v7.0.0\n      with:\n        name: controller-manifest\n\n    - name: Download container image artifact\n      uses: actions/download-artifact@v7.0.0\n      with:\n        name: controller-image\n\n    - name: Load docker image\n      run: |\n        eval $(minikube docker-env)\n        docker load -i controller-image.tar\n        docker inspect $CONTROLLER_IMAGE\n\n    - name: Testing environment setup\n      run: |\n        kubectl apply -f controller.yaml\n        kubectl rollout status deployment/sealed-secrets-controller -n kube-system -w --timeout=1m || kubectl -n kube-system describe pod -lname=sealed-secrets-controller\n\n    - name: Integration tests\n      run: make integrationtest CONTROLLER_IMAGE=$CONTROLLER_IMAGE GINKGO=\"ginkgo -v --randomizeSuites --failOnPending --trace --progress --compilers=2 --nodes=4\"\n\n  integration-chart:\n    needs: [ load-versions, container ]\n    name: Integration (Helm Chart)\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        k8s: [\"1.32.12\",\"1.33.8\",\"1.34.4\",\"1.35.1\"]\n    env:\n      MINIKUBE_WANTUPDATENOTIFICATION: \"false\"\n      MINIKUBE_WANTREPORTERRORPROMPT: \"false\"\n      CHANGE_MINIKUBE_NONE_USER: \"true\"\n    steps:\n    - name: \"Set environmental variables\"\n      run: |\n        echo \"CONTROLLER_IMAGE=$controller_registry/$controller_repository:$controller_tag\" >> $GITHUB_ENV\n\n    - name: Set up Go 1.x\n      uses: actions/setup-go@v6.3.0\n      with:\n        go-version: ${{ needs.load-versions.outputs.go_version }}\n      id: go\n\n    - name: Set up Ginkgo\n      run: |\n        go install github.com/onsi/ginkgo/ginkgo@v1.16.4\n\n    - name: Check out code into the Go module directory\n      uses: actions/checkout@v6.0.2\n\n    - uses: medyagh/setup-minikube@v0.0.21\n      with:\n        minikube-version: 1.38.0\n        kubernetes-version: ${{ matrix.k8s }}\n\n    - name: Install Helm\n      uses: azure/setup-helm@v3.5\n      with:\n        version: v3.12.0\n\n    # need to delete old state of the cluster, see:\n    # https://github.com/kubernetes/minikube/issues/8765\n    - name: K8s setup\n      run: |\n        minikube delete\n        minikube config set kubernetes-version v${{ matrix.k8s }}\n        minikube start --vm-driver=docker\n        minikube update-context\n        kubectl cluster-info\n\n    - name: Download container image artifact\n      uses: actions/download-artifact@v7.0.0\n      with:\n        name: controller-image\n\n    - name: Load docker image\n      run: |\n        eval $(minikube docker-env)\n        docker load -i controller-image.tar\n        docker inspect $CONTROLLER_IMAGE\n\n    - name: Testing environment setup\n      run: |\n        helm install sealed-secrets -n kube-system --set fullnameOverride=sealed-secrets-controller --set image.registry=$controller_registry --set image.repository=$controller_repository --set image.tag=$controller_tag --set image.pullPolicy=Never helm/sealed-secrets\n        kubectl rollout status deployment/sealed-secrets-controller -n kube-system -w --timeout=1m || kubectl -n kube-system describe pod -lapp.kubernetes.io/name=sealed-secrets\n\n    - name: Integration tests\n      run: make integrationtest CONTROLLER_IMAGE=$CONTROLLER_IMAGE GINKGO=\"ginkgo -v --randomizeSuites --failOnPending --trace --progress --compilers=2 --nodes=4\"\n"
  },
  {
    "path": ".github/workflows/cosign.pub",
    "content": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEseWNtEaI73oDVgjfLzU4eQYHE11i\nMzRSNs1TA+cTT/Lw70ckfCC/vHnOXKACF2dnhsZsNNj647p9mAiYNVl9ug==\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": ".github/workflows/helm-release.yaml",
    "content": "name: Release Helm Chart and Carvel package\n\non:\n  push:\n    paths:\n      # update this file to trigger helm chart release\n      - 'helm/sealed-secrets/Chart.yaml'\n    branches:\n      - main\n  workflow_dispatch:\n\njobs:\n  release:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: Configure Git\n        run: |\n          git config user.name \"$GITHUB_ACTOR\"\n          git config user.email \"$GITHUB_ACTOR@users.noreply.github.com\"\n\n      - name: Install Helm\n        uses: azure/setup-helm@v4.3.1\n        with:\n          version: v4.1.1\n\n      - name: Run chart-releaser\n        uses: helm/chart-releaser-action@v1.4.1\n        with:\n          charts_dir: helm\n        env:\n          CR_TOKEN: \"${{ secrets.GITHUB_TOKEN }}\"\n          CR_RELEASE_NAME_TEMPLATE: \"helm-v{{ .Version }}\"\n\n      - name: Install Carvel\n        uses: carvel-dev/setup-action@v1.3.0\n        with:\n          only: kbld, imgpkg\n          token: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Install yq\n        run: |\n          mkdir -p ~/bin\n          wget https://github.com/mikefarah/yq/releases/download/v4.30.8/yq_linux_amd64 -O ~/bin/yq\n          chmod +x ~/bin/yq\n\n      - name: Get chart version\n        run: |\n          export PATH=~/bin:$PATH\n          echo \"chart_version=$(yq .version < ./helm/sealed-secrets/Chart.yaml)\" >> $GITHUB_ENV\n\n      - name: Configure DNS for registry access\n        run: |\n          echo \"nameserver 8.8.8.8\" | sudo tee /etc/resolv.conf > /dev/null\n          echo \"nameserver 8.8.4.4\" | sudo tee -a /etc/resolv.conf > /dev/null\n\n      - name: OCI Push\n        env:\n          OCI_PASS: ${{ secrets.OCI_PASSWORD }}\n          OCI_USR: ${{ secrets.OCI_USERNAME }}\n          HELM_EXPERIMENTAL_OCI: 1\n        run: |\n          echo $OCI_PASS | helm registry login -u $OCI_USR --password-stdin registry-1.docker.io\n          helm package helm/sealed-secrets/\n          helm push sealed-secrets-${{ env.chart_version }}.tgz oci://registry-1.docker.io/bitnamicharts/sealed-secrets\n\n      - name: Create imglock file\n        working-directory: ./helm\n        run: |\n          mkdir -p .imgpkg\n          kbld -f <(helm template sealed-secrets) --imgpkg-lock-output .imgpkg/images.yml\n\n      - name: Push imgpkg bundle\n        working-directory: ./helm\n        env:\n          IMGPKG_REGISTRY_HOSTNAME: ghcr.io\n          IMGPKG_REGISTRY_USERNAME: ${{ github.actor }}\n          IMGPKG_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          imgpkg push -b ghcr.io/${{ github.repository_owner }}/sealed-secrets-carvel:${{ env.chart_version }} -f . --json > output\n          echo carvel_pkg=$(cat output | grep Pushed | cut -d \"'\" -f2 ) >> $GITHUB_ENV\n\n      - name: Update package.yaml\n        run: |\n          yq -i '.spec.version = \"${{ env.chart_version }}\"' carvel/package.yaml\n          yq -i '.metadata.name = \"sealedsecrets.bitnami.com.${{ env.chart_version }}\"' carvel/package.yaml\n          yq -i '.spec.template.spec.fetch.0.imgpkgBundle.image = \"${{ env.carvel_pkg }}\"' carvel/package.yaml\n          git checkout -B 'release-carvel-${{ env.chart_version }}'\n          git add carvel/package.yaml\n          git commit -sm 'Release carvel package ${{ env.chart_version }}'\n          git push origin 'release-carvel-${{ env.chart_version }}'\n\n      - name: Create PR\n        run: gh pr create --fill --base main --repo $GITHUB_REPOSITORY\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/helm-vib-lint.yaml",
    "content": "name: Lint Helm Chart\non:\n  workflow_dispatch:\n  pull_request_target:\n    branches:\n      - main\n      - bitnami-labs:main\n    paths:\n      - 'helm/**'\n\nenv:\n  CSP_API_URL: https://console.tanzu.broadcom.com\n  CSP_API_TOKEN: ${{ secrets.CSP_API_TOKEN }}\n  VIB_PUBLIC_URL: https://cp.app-catalog.vmware.com\n\njobs:\n  # make sure chart is linted/safe\n  vib-validate:\n    runs-on: ubuntu-latest\n    name: Lint chart\n    steps:\n      - uses: actions/checkout@v6.0.2\n        with:\n          ref: ${{github.event.pull_request.head.ref}}\n          repository: ${{github.event.pull_request.head.repo.full_name}}\n      - uses: vmware-labs/vmware-image-builder-action@v0.11.0\n"
  },
  {
    "path": ".github/workflows/helm-vib.yaml",
    "content": "name: Verify Helm Chart\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n    paths:\n       - 'helm/**'\n\nenv:\n  CSP_API_URL: https://console.tanzu.broadcom.com\n  CSP_API_TOKEN: ${{ secrets.CSP_API_TOKEN }}\n  VIB_PUBLIC_URL: https://cp.app-catalog.vmware.com\n\njobs:\n  # verify chart in multiple target platforms\n  vib-k8s-verify:\n    runs-on: ubuntu-latest\n    environment: vmware-image-builder\n    strategy:\n      matrix:\n       include:\n         - name: Openshift\n           target-platform: openshift\n           target-platform-id: ebac9e0d-3931-4515-ba54-e6adada1f174\n           target-pipeline: vib-platform-verify-openshift.json\n      fail-fast: false\n    name: Verify chart (${{ matrix.name }})\n    steps:\n      - uses: actions/checkout@v6.0.2\n        with:\n          ref: ${{ github.event.pull_request.head.ref }}\n          repository: ${{ github.event.pull_request.head.repo.full_name }}\n      - uses: vmware-labs/vmware-image-builder-action@v0.11.0\n        with:\n          pipeline: ${{ matrix.target-pipeline }}\n          max-pipeline-duration: 7200\n        env:\n          TARGET_PLATFORM: ${{ matrix.target-platform-id }}\n"
  },
  {
    "path": ".github/workflows/publish-release.yaml",
    "content": "name: Publish Release\n\non:\n  workflow_dispatch:\n    inputs:\n      chart:\n        description: 'Chart version (e.g. 2.11.3)'\n        required: true\n        type: string\n\njobs:\n  chart-pr:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: Config Git\n        run: |\n          git config user.name \"$GITHUB_ACTOR\"\n          git config user.email \"$GITHUB_ACTOR@users.noreply.github.com\"\n\n      - name: Fetch Versions\n        run: |\n          echo NEW_VERSION=$(git describe --tags --match \"v[0-9]*\" --abbrev=0 | tr -d v) >> \"$GITHUB_ENV\"\n          echo PREV_VERSION=$(grep appVersion helm/sealed-secrets/Chart.yaml | grep -o '[0-9.]*') >> \"$GITHUB_ENV\"\n\n      - name: Update Version\n        run: |\n          sed -i \"s/version: .*/version: ${{ inputs.chart }}/\" helm/sealed-secrets/Chart.yaml\n          sed -i \"s/appVersion: .*/appVersion: $NEW_VERSION/\" helm/sealed-secrets/Chart.yaml\n          sed -i \"s/tag: .*/tag: $NEW_VERSION/\" helm/sealed-secrets/values.yaml\n          sed -i \"s/\\`$PREV_VERSION\\`/\\`$NEW_VERSION\\`/\" helm/sealed-secrets/README.md\n          git checkout -B 'release-chart-${{ inputs.chart }}'\n          git add helm/sealed-secrets/Chart.yaml helm/sealed-secrets/values.yaml helm/sealed-secrets/README.md\n          git commit -sm 'Release chart ${{ inputs.chart }}'\n          git push origin 'release-chart-${{ inputs.chart }}'\n\n      - name: Create PR\n        run: gh pr create --fill --base main --repo $GITHUB_REPOSITORY\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/release.yaml",
    "content": "name: Prepare Release\n\n# Only release when a new GH release branch is pushed\non:\n  push:\n    branches:\n      - 'release/v[0-9]+.[0-9]+.[0-9]+'\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    env:\n      controller_dockerhub_image_name: docker.io/bitnami/sealed-secrets-controller\n      controller_ghcr_image_name: ghcr.io/bitnami-labs/sealed-secrets-controller\n      kubeseal_dockerhub_image_name: docker.io/bitnami/sealed-secrets-kubeseal\n      kubeseal_ghcr_image_name: ghcr.io/bitnami-labs/sealed-secrets-kubeseal\n    steps:\n      # Checkout and set env\n      - name: Checkout\n        uses: actions/checkout@v6.0.2\n        with:\n          fetch-depth: 0\n      - id: load-version\n        run: |\n          source $GITHUB_WORKSPACE/versions.env\n          echo \"GO_VERSION=$GO_VERSION\" >> $GITHUB_ENV\n      - name: Set up Go\n        uses: actions/setup-go@v6.3.0\n        with:\n          go-version: ${{ env.GO_VERSION }}\n      - name: Setup kubecfg\n        run: |\n          mkdir -p ~/bin\n          curl -sLf https://github.com/kubecfg/kubecfg/releases/download/v0.26.0/kubecfg_Linux_X64 >~/bin/kubecfg\n          chmod +x ~/bin/kubecfg\n\n      - name: Install dependencies\n        run: |\n          go install gotest.tools/gotestsum@v1.8.1\n\n      # Run tests\n      - name: Tests\n        run: make test\n\n      # Generate K8s manifests\n      - name: K8s manifests\n        run: |\n          export PATH=~/bin:$PATH\n          RELEASE_BRANCH=\"${{ github.ref }}\"\n          VERSION_TAG=$(echo \"${RELEASE_BRANCH}\" | awk -F'/' '{print $NF}')\n          echo \"VERSION_TAG=$VERSION_TAG\" >> $GITHUB_ENV\n          IMAGE_TAG=$(echo \"${VERSION_TAG}\" | sed 's/^v//')\n          echo \"IMAGE_TAG=$IMAGE_TAG\" >> $GITHUB_ENV\n          make CONTROLLER_IMAGE=${{ env.controller_dockerhub_image_name }}:${IMAGE_TAG} controller.yaml controller-norbac.yaml\n\n      # Setup env for multi-arch builds\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v2.0.0\n        with:\n          image: tonistiigi/binfmt:latest\n          platforms: arm64,arm\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v2.0.0\n\n      # Setup Cosign\n      - name: Install Cosign\n        uses: sigstore/cosign-installer@v3.0.2\n      - name: Write Cosign key\n        run: echo \"$COSIGN_KEY\" > /tmp/cosign.key\n        env:\n          COSIGN_KEY: ${{ secrets.COSIGN_KEY }}\n\n      # Tag for GoReleaser from release branch name\n      - name: Tag Release\n        run: |\n          git tag \"${VERSION_TAG}\"\n\n      # Build & Release binaries\n      - name: Run GoReleaser\n        uses: goreleaser/goreleaser-action@v6\n        if: success() && startsWith(github.ref, 'refs/heads/')\n        with:\n          distribution: goreleaser\n          version: v2.11.2\n          args: release --clean\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}\n\n      # Build & Publish multi-arch image\n      - name: Login to Docker Hub\n        uses: docker/login-action@v2.0.0\n        with:\n          username: ${{ secrets.BITNAMI_USERNAME }}\n          password: ${{ secrets.BITNAMI_PASSWORD }}\n      - name: Login to GHRC\n        uses: docker/login-action@v2.0.0\n        with:\n          registry: ghcr.io\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n      - name: Extract metadata (tags, labels) for Docker controller image\n        id: meta_controller\n        uses: docker/metadata-action@v4.0.1\n        with:\n          images: |\n            ${{ env.controller_dockerhub_image_name }}\n            ${{ env.controller_ghcr_image_name }}\n          tags: |\n            type=raw,value=${{ env.IMAGE_TAG }}\n            type=raw,value=latest\n      - name: Build and push controller image\n        id: docker_build_controller\n        uses: docker/build-push-action@v3.2.0\n        with:\n          context: .\n          file: ./docker/controller.Dockerfile\n          platforms: linux/amd64,linux/arm64,linux/arm\n          push: true\n          tags: ${{ steps.meta_controller.outputs.tags }}\n      - name: Extract metadata (tags, labels) for Docker kubeseal image\n        id: meta_kubeseal\n        uses: docker/metadata-action@v4.0.1\n        with:\n          images: |\n            ${{ env.kubeseal_dockerhub_image_name }}\n            ${{ env.kubeseal_ghcr_image_name }}\n          tags: |\n            type=raw,value=${{ env.IMAGE_TAG }}\n            type=raw,value=latest\n      - name: Build and push kubeseal image\n        id: docker_build_kubeseal\n        uses: docker/build-push-action@v3.2.0\n        with:\n          context: .\n          file: ./docker/kubeseal.Dockerfile\n          platforms: linux/amd64,linux/arm64,linux/arm\n          push: true\n          tags: ${{ steps.meta_kubeseal.outputs.tags }}\n      - name: Sign controller image with a key in GHCR\n        run: |\n          echo -n \"$COSIGN_PASSWORD\" | cosign sign --key /tmp/cosign.key --yes $TAG_CURRENT\n        env:\n          COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}\n          TAG_CURRENT: ${{ steps.meta_controller.outputs.tags }}\n          COSIGN_REPOSITORY: ${{ env.controller_ghcr_image_name }}/signs\n      - name: Sign kubeseal image with a key in GHCR\n        run: |\n          echo -n \"$COSIGN_PASSWORD\" | cosign sign --key /tmp/cosign.key --yes $TAG_CURRENT\n        env:\n          COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}\n          TAG_CURRENT: ${{ steps.meta_kubeseal.outputs.tags }}\n          COSIGN_REPOSITORY: ${{ env.kubeseal_ghcr_image_name }}/signs\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: 'Close stale issues and PRs'\non:\n  schedule:\n    # Stalebot will be executed at 1:00 AM every day\n    - cron: '0 1 * * *'\n\njobs:\n  stale:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/stale@v10.1.1\n        with:\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n          stale-issue-message: 'This Issue has been automatically marked as \"stale\" because it has not had recent activity (for 15 days). It will be closed if no further activity occurs. Thanks for the feedback.'\n          stale-pr-message: 'This Pull Request has been automatically marked as \"stale\" because it has not had recent activity (for 15 days). It will be closed if no further activity occurs. Thank you for your contribution.'\n          close-issue-message: 'Due to the lack of activity in the last 7 days since it was marked as \"stale\", we proceed to close this Issue. Do not hesitate to reopen it later if necessary.'\n          close-pr-message: 'Due to the lack of activity in the last 7 days since it was marked as \"stale\", we proceed to close this Pull Request. Do not hesitate to reopen it later if necessary.'\n          days-before-stale: 15\n          days-before-close: 7\n          exempt-issue-labels: 'backlog,help wanted,triage'\n          exempt-pr-labels: 'backlog,help wanted,triage'\n          operations-per-run: 500\n"
  },
  {
    "path": ".gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.dll\n*.so\n*.dylib\n\n# Test binary, build with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736\n.glide/\n\n# Project-local vscode config\n.vscode/\n\n/controller\n/kubeseal\n/kubeseal-arm\n/kubeseal-arm64\n\n/controller.image\n/controller.image.*\n/kubeseal.image\n/kubeseal.image.*\n/pushed.controller.image.*\n/pushed.kubeseal.image.*\n/controller-manifest-*\n/push-controller-image\n/*-static\n/*-static-*\n/controller.yaml\n/controller-norbac.yaml\n/controller-podmonitor.yaml\n/docker/controller\n*.iml\n.idea\n\n# GoReleaser output dir\ndist/\n\n# Vendor folder\nvendor/\nreport.xml\n"
  },
  {
    "path": ".golangci.yaml",
    "content": "# Inspired by https://gist.github.com/maratori/47a4d00457a92aa426dbd48a18776322\n\n# output configuration options\noutput:\n  # Format: colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml|github-actions\n  # Default: colored-line-number\n  format: checkstyle:report.xml,colored-line-number:stdout\n\n# Options for analysis running.\nrun:\n  # Timeout for analysis, e.g. 30s, 5m.\n  # Default: 1m\n  timeout: 5m\n\n# This file contains only configs which differ from defaults.\n# All possible options can be found here https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml\nlinters-settings:\n  cyclop:\n    # The maximal code complexity to report.\n    # Default: 10\n    max-complexity: 30\n    # The maximal average package complexity.\n    # If it's higher than 0.0 (float) the check is enabled\n    # Default: 0.0\n    package-average: 10.0\n\n  errcheck:\n    # Report about not checking of errors in type assertions: `a := b.(MyStruct)`.\n    # Such cases aren't reported by default.\n    # Default: false\n    check-type-assertions: true\n\n    # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`.\n    # Such cases aren't reported by default.\n    # Default: false\n    check-blank: true\n\n  exhaustive:\n    # Program elements to check for exhaustiveness.\n    # Default: [ switch ]\n    check:\n      - switch\n      - map\n\n  exhaustruct:\n    # List of regular expressions to exclude struct packages and names from check.\n    # Default: []\n    exclude:\n      # std libs\n      - \"^net/http.Client$\"\n      - \"^net/http.Cookie$\"\n      - \"^net/http.Request$\"\n      - \"^net/http.Response$\"\n      - \"^net/http.Server$\"\n      - \"^net/http.Transport$\"\n      - \"^net/url.URL$\"\n      - \"^os/exec.Cmd$\"\n      - \"^reflect.StructField$\"\n      # public libs (add more if needed)\n\n  funlen:\n    # Checks the number of lines in a function.\n    # If lower than 0, disable the check.\n    # Default: 60\n    lines: 100\n    # Checks the number of statements in a function.\n    # If lower than 0, disable the check.\n    # Default: 40\n    statements: 50\n\n  gocognit:\n    # Minimal code complexity to report.\n    # Default: 30 (but we recommend 10-20)\n    min-complexity: 20\n\n  goconst:\n    # Minimal length of string constant.\n    # Default: 3\n    min-len: 2\n    # Minimum occurrences of constant string count to trigger issue.\n    # Default: 3\n    min-occurrences: 2\n    # Search also for duplicated numbers.\n    # Default: false\n    numbers: true\n    # Minimum value, only works with goconst.numbers\n    # Default: 3\n    min: 2\n\n  gocritic:\n    # Settings passed to gocritic.\n    # The settings key is the name of a supported gocritic checker.\n    # The list of supported checkers can be find in https://go-critic.github.io/overview.\n    settings:\n      captLocal:\n        # Whether to restrict checker to params only.\n        # Default: true\n        paramsOnly: false\n      underef:\n        # Whether to skip (*x).method() calls where x is a pointer receiver.\n        # Default: true\n        skipRecvDeref: false\n\n  gomnd:\n    # List of function patterns to exclude from analysis.\n    # Values always ignored: `time.Date`,\n    # `strconv.FormatInt`, `strconv.FormatUint`, `strconv.FormatFloat`,\n    # `strconv.ParseInt`, `strconv.ParseUint`, `strconv.ParseFloat`.\n    # Default: []\n    ignored-functions:\n      - os.Chmod\n      - os.Mkdir\n      - os.MkdirAll\n      - os.OpenFile\n      - os.WriteFile\n      - math.*\n      - http.StatusText\n\n  govet:\n    # Enable all analyzers.\n    # Default: false\n    enable-all: true\n\n    # Disable analyzers by name.\n    # Run `go tool vet help` to see all analyzers.\n    # Default: []\n    disable:\n      - fieldalignment # too strict, it warns about struct fields that are not aligned by size\n\n    # Settings per analyzer.\n    settings:\n      shadow:\n        # Whether to be strict about shadowing; can be noisy.\n        # Default: false\n        strict: true\n\n  nakedret:\n    # Make an issue if func has more lines of code than this setting, and it has naked returns.\n    # Default: 30\n    max-func-lines: 0\n\n  nestif:\n    # Minimal complexity of if statements to report.\n    # Default: 5\n    min-complexity: 4\n\n  nolintlint:\n    # Exclude following linters from requiring an explanation.\n    # Default: []\n    allow-no-explanation: [funlen, gocognit, lll]\n    # Enable to require an explanation of nonzero length after each nolint directive.\n    # Default: false\n    require-explanation: true\n    # Enable to require nolint directives to mention the specific linter being suppressed.\n    # Default: false\n    require-specific: true\n\n  lll:\n    # Max line length, lines longer will be reported.\n    # '\\t' is counted as 1 character by default, and can be changed with the tab-width option.\n    # Default: 120.\n    line-length: 240\n\n  rowserrcheck:\n    # database/sql is always checked\n    # Default: []\n    packages:\n      - github.com/jmoiron/sqlx\n\n  tenv:\n    # The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures.\n    # Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked.\n    # Default: false\n    all: true\n\n  varnamelen:\n    # The minimum length of a variable's name that is considered \"long\".\n    # Variable names that are at least this long will be ignored.\n    # Default: 3\n    min-name-length: 2\n    # Check method receivers.\n    # Default: false\n    # Ignore \"ok\" variables that hold the bool return value of a type assertion.\n    # Default: false\n    ignore-type-assert-ok: true\n    # Ignore \"ok\" variables that hold the bool return value of a map index.\n    # Default: false\n    ignore-map-index-ok: true\n    # Ignore \"ok\" variables that hold the bool return value of a channel receive.\n    # Default: false\n    ignore-chan-recv-ok: true\n\n  godot:\n    # Check periods at the end of sentences.\n    period: false\n\nlinters:\n  disable-all: true\n  enable:\n    #- errcheck # checking for unchecked errors, these unchecked errors can be critical bugs in some cases\n    - gosimple # specializes in simplifying a code\n    #- govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string\n    - ineffassign # detects when assignments to existing variables are not used\n    - staticcheck # is a go vet on steroids, applying a ton of static analysis checks\n    - typecheck # like the front-end of a Go compiler, parses and type-checks Go code\n    - unused # checks for unused constants, variables, functions and types\n    - asasalint # checks for pass []any as any in variadic func(...any)\n    - asciicheck # checks that your code does not contain non-ASCII identifiers\n    - bidichk # checks for dangerous unicode character sequences\n    - bodyclose # checks whether HTTP response body is closed successfully\n    #- cyclop # checks function and package cyclomatic complexity\n    #- dupl # tool for code clone detection\n    - durationcheck # checks for two durations multiplied together\n    - errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error\n    #- errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13\n    #- execinquery # checks query string in Query function which reads your Go src files and warning it finds\n    - exhaustive # checks exhaustiveness of enum switch statements\n    #- exportloopref # checks for pointers to enclosing loop variables\n    #- forbidigo # forbids identifiers\n    #- funlen # tool for detection of long functions\n    #- gochecknoinits # checks that no init functions are present in Go code\n    #- gocognit # computes and checks the cognitive complexity of functions\n    #- goconst # finds repeated strings that could be replaced by a constant\n    #- gocritic # provides diagnostics that check for bugs, performance and style issues\n    #- gocyclo # computes and checks the cyclomatic complexity of functions\n    - godot # checks if comments end in a period\n    - goimports # in addition to fixing imports, goimports also formats your code in the same style as gofmt\n    #- gomnd # detects magic numbers\n    - gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod\n    - gomodguard # allow and block lists linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations\n    - goprintffuncname # checks that printf-like functions are named with f at the end\n    #- gosec # inspects source code for security problems\n    #- lll # reports long lines\n    - loggercheck # checks key value pairs for common logger libraries (kitlog,klog,logr,zap)\n    #- makezero # finds slice declarations with non-zero initial length\n    - nakedret # finds naked returns in functions greater than a specified function length\n    #- nestif # reports deeply nested if statements\n    #- nilerr # finds the code that returns nil even if it checks that the error is not nil\n    - nilnil # checks that there is no simultaneous return of nil error and an invalid value\n    #- noctx # finds sending http request without context.Context\n    - nolintlint # reports ill-formed or insufficient nolint directives\n    #- nonamedreturns # reports all named returns\n    - nosprintfhostport # checks for misuse of Sprintf to construct a host with port in a URL\n    #- predeclared # finds code that shadows one of Go's predeclared identifiers\n    - promlinter # checks Prometheus metrics naming via promlint\n    - reassign # checks that package variables are not reassigned\n    #- revive # fast, configurable, extensible, flexible, and beautiful linter for Go, drop-in replacement of golint\n    - rowserrcheck # checks whether Err of rows is checked successfully\n    - sqlclosecheck # checks that sql.Rows and sql.Stmt are closed\n    #- stylecheck # is a replacement for golint\n    - tenv # detects using os.Setenv instead of t.Setenv since Go1.17\n    - testableexamples # checks if examples are testable (have an expected output)\n    #- testpackage # makes you use a separate _test package\n    - tparallel # detects inappropriate usage of t.Parallel() method in your Go test codes\n    - unconvert # removes unnecessary type conversions\n    #- unparam # reports unused function parameters\n    - usestdlibvars # detects the possibility to use variables/constants from the Go standard library\n    - wastedassign # finds wasted assignment statements\n    - whitespace # detects leading and trailing whitespace\n\n    ## you may want to enable\n    - decorder # checks declaration order and count of types, constants, variables and functions\n    #- gci # controls golang package import order and makes it always deterministic\n    - goheader # checks is file header matches to pattern\n    - interfacebloat # checks the number of methods inside an interface\n    #- prealloc # [premature optimization, but can be used in some cases] finds slice declarations that could potentially be preallocated\n    #- varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope\n    #- wrapcheck # checks that errors returned from external packages are wrapped\n    #- containedctx # detects struct contained context.Context field\n    - contextcheck # [too many false positives] checks the function whether use a non-inherited context\n    - dogsled # checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())\n    #- dupword # [useless without config] checks for duplicate words in the source code\n    - errchkjson # [don't see profit + I'm against of omitting errors like in the first example https://github.com/breml/errchkjson] checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted\n    #- goerr113 # [too strict] checks the errors handling expressions\n    - grouper # analyzes expression groups\n    - importas # enforces consistent import aliases\n    - maintidx # measures the maintainability index of each function\n    - misspell # [useless] finds commonly misspelled English words in comments\n    #- nlreturn # [too strict and mostly code is not more readable] checks for a new line before return and branch statements to increase code clarity\n    #- paralleltest # [too many false positives] detects missing usage of t.Parallel() method in your Go test\n    - tagliatelle # checks the struct tags\n    #- thelper # detects golang test helpers without t.Helper() call and checks the consistency of test helpers\n    #- wsl # [too strict and mostly code is not more readable] whitespace linter forces you to use empty lines\n\n    ## disabled\n    # - exhaustruct # [highly recommend to enable] checks if all structure fields are initialized\n    # - godox # detects FIXME, TODO and other comment keywords\n    # - gochecknoglobals # checks that no global variables exist\n    # - ireturn # accept interfaces, return concrete types\n\nissues:\n  # Maximum count of issues with the same text.\n  # Set to 0 to disable.\n  # Default: 3\n  max-same-issues: 0\n"
  },
  {
    "path": ".goreleaser.yml",
    "content": "project_name: sealed-secrets\nenv:\n  - CGO_ENABLED=0\nbuilds:\n  - binary: controller\n    id: controller\n    main: ./cmd/controller\n    ldflags:\n      - -X main.VERSION={{ .Version }}\n    targets:\n      - darwin_amd64\n      - darwin_arm64\n      - linux_amd64\n      - linux_arm64\n      - linux_arm\n      - windows_amd64\n  - binary: kubeseal\n    id: kubeseal\n    main: ./cmd/kubeseal\n    ldflags:\n      - -X main.VERSION={{ .Version }}\n    targets:\n      - darwin_amd64\n      - darwin_arm64\n      - linux_amd64\n      - linux_arm64\n      - linux_arm\n      - windows_amd64\narchives:\n  - builds:\n      - kubeseal\n    name_template: \"kubeseal-{{ .Version }}-{{ .Os }}-{{ .Arch }}\"\nchecksum:\n  algorithm: sha256\nchangelog:\n  sort: asc\n  filters:\n    exclude:\n      - '^docs:'\n      - '^helm:'\n      - '^integration:'\n      - '^vendor_jsonnet:'\nsigns:\n  - cmd: cosign\n    stdin: '{{ .Env.COSIGN_PASSWORD }}'\n    output: true\n    artifacts: all\n    args:\n      - 'sign-blob'\n      - '--key=/tmp/cosign.key'\n      - '--output-signature=${signature}'\n      - '--yes'\n      - '${artifact}'\nrelease:\n  name_template: \"{{ .ProjectName }}-v{{ .Version }}\"\n  header: |\n    ## v{{ .Version }} ({{ .Date }})\n\n    New v{{ .Version }} release!\n  footer: |\n    ## Installation Instructions\n\n    ### Cluster-side\n\n    Install the SealedSecret CRD and server-side controller into the `kube-system` namespace:\n\n    ```sh\n    kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v{{ .Version }}/controller.yaml\n    ```\n\n    ### Client-side\n\n    Install the client-side tool into `/usr/local/bin/`:\n\n    **Linux x86_64:**\n    ```sh\n    curl -OL \"https://github.com/bitnami-labs/sealed-secrets/releases/download/v{{ .Version }}/kubeseal-{{ .Version }}-linux-amd64.tar.gz\"\n    tar -xvzf kubeseal-{{ .Version }}-linux-amd64.tar.gz kubeseal\n    sudo install -m 755 kubeseal /usr/local/bin/kubeseal\n    ```\n\n    **macOS:**\n    The `kubeseal` client is available on [homebrew](https://formulae.brew.sh/formula/kubeseal):\n\n    ```sh\n    brew install kubeseal\n    ```\n\n    **MacPorts:**\n\n    The `kubeseal` client is available on [MacPorts](https://ports.macports.org/port/kubeseal/summary):\n\n    ```sh\n    port install kubeseal\n    ```\n\n    #### Nixpkgs\n\n    The `kubeseal` client is available on [Nixpkgs](https://search.nixos.org/packages?channel=unstable&show=kubeseal&from=0&size=50&sort=relevance&type=packages&query=kubeseal): (**DISCLAIMER**: Not maintained by bitnami-labs)\n\n    ```sh\n    nix-env -iA nixpkgs.kubeseal\n    ```\n\n    **Other OS/Arch:**\n    Binaries for other OS/arch combinations are attached to this release below.\n\n    If you just want the latest client tool, it can be installed into\n    `$GOPATH/bin` with:\n\n    ```sh\n    go install github.com/bitnami-labs/sealed-secrets/cmd/kubeseal@main\n    ```\n\n    You can specify a release tag or a commit SHA instead of `main`.\n\n    The `go install` command will place the `kubeseal` binary at `$GOPATH/bin`:\n\n    ```sh\n    $(go env GOPATH)/bin/kubeseal\n    ```\n\n    ## Release Notes\n\n    Please read the [RELEASE_NOTES](https://github.com/bitnami-labs/sealed-secrets/blob/main/RELEASE-NOTES.md) which contain among other things important information for those upgrading from previous releases.\n    ## Thanks!\n\n  extra_files:\n    - glob: ./controller.yaml\n    - glob: ./controller-norbac.yaml\n    - glob: ./.github/workflows/cosign.pub\n"
  },
  {
    "path": ".vib/vib-pipeline.json",
    "content": "{\n  \"phases\": {\n    \"package\": {\n      \"context\": {\n        \"resources\": {\n          \"url\": \"{SHA_ARCHIVE}\",\n          \"path\": \"/helm/sealed-secrets\"\n        }\n      },\n      \"actions\": [\n        {\n          \"action_id\": \"helm-package\"\n        },\n        {\n          \"action_id\": \"helm-lint\"\n        }\n      ]\n    },\n    \"verify\": {\n      \"context\": {\n        \"runtime_parameters\": \"IyMgQ3JlYXRlIFNlYWxlZCBTZWNyZXRzIGNvbnRyb2xsZXIgc2hvdWxkIGJlIGNyZWF0ZWQKY3JlYXRlQ29udHJvbGxlcjogdHJ1ZQojIyBTZWNyZXQgY29udGFpbmluZyB0aGUga2V5IHVzZWQgdG8gZW5jcnlwdCBzZWNyZXRzCnNlY3JldE5hbWU6ICJzZWFsZWQtc2VjcmV0cy1rZXkiCiMjIFJlbmV3IGtleXMgZXZlcnkgd2VlawprZXlyZW5ld3BlcmlvZDogIjE2OGgiCg==\"\n      },\n      \"actions\": [\n        {\n          \"action_id\": \"trivy\",\n          \"params\": {\n            \"threshold\": \"CRITICAL\",\n            \"vuln_type\": [\"OS\"]\n          }\n        }\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": ".vib/vib-platform-verify-openshift.json",
    "content": "{\n  \"phases\": {\n    \"package\": {\n      \"context\": {\n        \"resources\": {\n          \"url\": \"{SHA_ARCHIVE}\",\n          \"path\": \"/helm/sealed-secrets\"\n        }\n      },\n      \"actions\": [\n        {\n          \"action_id\": \"helm-package\"\n        }\n      ]\n    },\n    \"verify\": {\n      \"context\": {\n        \"resources\": {\n          \"url\": \"{SHA_ARCHIVE}\",\n          \"path\": \"/.vib/\"\n        },\n        \"runtime_parameters\": \"IyMgQ3JlYXRlIFNlYWxlZCBTZWNyZXRzIGNvbnRyb2xsZXIgc2hvdWxkIGJlIGNyZWF0ZWQKY3JlYXRlQ29udHJvbGxlcjogdHJ1ZQojIyBTZWNyZXQgY29udGFpbmluZyB0aGUga2V5IHVzZWQgdG8gZW5jcnlwdCBzZWNyZXRzCnNlY3JldE5hbWU6ICJzZWFsZWQtc2VjcmV0cy1rZXkiCiMjIFJlbmV3IGtleXMgZXZlcnkgd2VlawprZXlyZW5ld3BlcmlvZDogIjE2OGgiCmNvbnRhaW5lclNlY3VyaXR5Q29udGV4dDoKICBlbmFibGVkOiB0cnVlCiAgcmVhZE9ubHlSb290RmlsZXN5c3RlbTogdHJ1ZQogIHJ1bkFzTm9uUm9vdDogdHJ1ZQogIHJ1bkFzVXNlcjogbnVsbApwb2RTZWN1cml0eUNvbnRleHQ6CiAgZW5hYmxlZDogZmFsc2UKc2VydmljZToKICB0eXBlOiBMb2FkQmFsYW5jZXIKICBwb3J0OiA4MAo=\",\n        \"target_platform\": {\n          \"target_platform_id\": \"{TARGET_PLATFORM}\"\n        }\n      },\n      \"actions\": [\n        {\n          \"action_id\": \"health-check\",\n          \"params\": {\n            \"endpoint\": \"lb-sealed-secrets-http\"\n          }\n        }\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\nsealed-secrets.pdl@broadcom.com.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior,  harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing Guidelines\n\nContributions are welcome via GitHub Pull Requests. This document outlines the process to help get your contribution accepted.\n\nAny type of contribution is welcome; from new features, bug fixes, or documentation improvements. However, VMware/Bitnami will review the proposals and perform a triage over them. By doing so, we will ensure that the most valuable contributions for the community will be implemented in due time.\n\n## How to Contribute\n\n1. Fork this repository, develop, and test your changes.\n2. Submit a pull request.\n\n### Technical Requirements\n\nWhen submitting a PR make sure that it:\n\n- Must pass CI jobs for linting and test the changes on top of different k8s platforms.\n- Must follow [Golang best practices](https://go.dev/doc/effective_go).\n- Is signed off with the line `Signed-off-by: <Your-Name> <Your-email>`. See [related GitHub blogpost about signing off](https://github.blog/changelog/2022-06-08-admins-can-require-sign-off-on-web-based-commits/).\n  > Note: Signing off on a commit is different than signing a commit, such as with a GPG key.\n\n### PR Approval\n\n1. Changes are manually reviewed by VMware/Bitnami team members.\n2. When the PR passes all tests, the PR is merged by the reviewer(s) in the GitHub `main` branch.\n\n### Release process\n\nThe release process is based upon periodic release trains.\n\n#### Schedule\n\nReleases happen monthly. A release train \"leaves\" on the 15th of each month, or the closest working date to that.\n \n#### Creation\n\nFirst of all, prepare the release notes as usual, and merge them.\n\nOnce the release notes are ready, a release train is launched by *branching* from `main` to `release/vX.Y.Z`.\n\n#### Validation\n\nThe `release/vX.Y.Z` branch will go through the release CI. GoReleaser requires a tag to build a release, so one will be produced automatically from the release branch name `vX.Y.Z`.\n\nIf anything fails the release branch is dropped, the issue fixed in `main` and a new release train is started on a new branch.\n\n#### Tracking\n\nOnce the release passes all validations and is published, it is merged into `released`.\n\nNote that currently the release process is done in 2 steps, first the container images, then the chart using them. Both events must be merged in the `released` branch.\n\n#### Hot-fixing releases\n\nIf there is a need to urgently fix a show-stopper issue in the latest released version. There is no need to wait for the next release train for a new release to happen.\n\nUnless there is a strong reason not to, a fix can be merged into `main` directly, followed by a regular release process.\n\nIf doing the fix in main is a \"no go\" for some reason, for instance, a new change already in `main` makes the bug to be urgently fixed even worse, then the fix must happen from the latest released code to proceed ASAP:\n\n* Create a `hotfix/YYYYMMDD` branch as a copy of `released`. The `YYYYMMDD` suffix is an ISO-8601 timestamp, for tracking purposes.\n* Branch off `hotfix/YYYYMMDD` to work on the fix. As a regular PR, you might name the fix branch with a descriptive name for the bug being fixed.\n* Once the fix is approved and tested as successful, merge into `hotfix/YYYYMMDD`.\n* Push `hostfix/YYYYMMDD` as a `release/vX.Y.Z` to kick off a release train.\n* If the release fails for any reason, fix it in `hostfix/YYYYMMDD`, merge and push another `release/vX.Y.Z'` branch.\n* Once a hotfix release completes successfully, merge the `release/vX.Y.Z` as `released` as per normal procedure.\n* *Backport the hotfix into the `main` including the tests added to detected regressions* of that bug going forward.\n* Finally, `hotfix/YYYYMMDD` can be kept around for tracking or historical purposes.\n\nNote that, in either case, the release notes must clarify this was a hotfix our of the regular release train schedule.\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} {name of copyright owner}\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"
  },
  {
    "path": "MAINTAINERS.md",
    "content": "# Sealed Secrets Maintainers\n\n## Maintainers\n\n| Maintainer         | GitHub ID                                           |                              Affiliation |\n| ------------------ | --------------------------------------------------- | ---------------------------------------: |\n| Alvaro Neira Ayuso | [alvneiayu](https://github.com/alvneiayu)           | [VMware](https://www.github.com/vmware/) |\n| Alejandro Moreno   | [alemorcuq](https://github.com/alemorcuq)           | [VMware](https://www.github.com/vmware/) |\n| Alfredo Garcia     | [agarcia-oss](https://github.com/agarcia-oss)       | [VMware](https://www.github.com/vmware/) |\n\n## Emeritus Maintainers\n\n- Angus Lees ([anguslees](https://github.com/anguslees))\n- Marko Mikulicic ([mkmik](https://github.com/mkmik))\n- Juan Ariza ([juan131](https://github.com/juan131))\n- Jose Vazquez ([josvazg](https://github.com/josvazg))\n\n---\n\nFull list of [Sealed Secrets contributors](https://github.com/bitnami-labs/sealed-secrets/graphs/contributors).\n"
  },
  {
    "path": "Makefile",
    "content": "GO = go\nGOTESTSUM = gotestsum\nGOFMT = gofmt\nGOLANGCILINT=golangci-lint -vv\nGOSEC=gosec\n\nexport GO111MODULE = on\nGO_FLAGS =\n\nKUBECFG = kubecfg\nDOCKER = docker\nGINKGO = ginkgo -p\nCONTROLLER_GEN ?= go run sigs.k8s.io/controller-tools/cmd/controller-gen@latest\n\nREGISTRY ?= docker.io\nCONTROLLER_IMAGE = $(REGISTRY)/bitnami/sealed-secrets-controller:latest\nKUBESEAL_IMAGE = $(REGISTRY)/bitnami/sealed-secrets-kubeseal:latest\nINSECURE_REGISTRY = false # useful for local registry\nIMAGE_PULL_POLICY =\nKUBECONFIG ?= $(HOME)/.kube/config\n\nGO_PACKAGES = ./...\nGO_FILES := $(shell find $(shell $(GO) list -f '{{.Dir}}' $(GO_PACKAGES)) -name \\*.go)\n\nCOMMIT = $(shell git rev-parse HEAD)\nTAG = $(shell git describe --exact-match --abbrev=0 --tags '$(COMMIT)' 2> /dev/null || true)\nDIRTY = $(shell git diff --shortstat 2> /dev/null | tail -n1)\n\n# Use a tag if set, otherwise use the commit hash\nifeq ($(TAG),)\nVERSION := $(COMMIT)\nelse\nVERSION := $(TAG)\nendif\n\nGOOS = $(shell go env GOOS)\nGOARCH = $(shell go env GOARCH)\n\n# Check for changed files\nifneq ($(DIRTY),)\nVERSION := $(VERSION)+dirty\nendif\n\nGO_LD_FLAGS = -X main.VERSION=$(VERSION)\n\nall: controller kubeseal\n\ngenerate:\n\t$(GO) mod vendor\n\t./hack/update-codegen.sh\n\trm -rf vendor\n\nmanifests:\n\t$(CONTROLLER_GEN) crd:generateEmbeddedObjectMeta=true paths=\"./pkg/apis/...\" output:stdout | tail -n +2 > helm/sealed-secrets/crds/bitnami.com_sealedsecrets.yaml\n\tyq '.spec.versions[0].schema' < helm/sealed-secrets/crds/bitnami.com_sealedsecrets.yaml > schema-v1alpha1.yaml\n\ncontroller: $(GO_FILES)\n\t$(GO) build -o $@ $(GO_FLAGS) -ldflags \"$(GO_LD_FLAGS)\" ./cmd/controller\n\nkubeseal: $(GO_FILES)\n\t$(GO) build -o $@ $(GO_FLAGS) -ldflags \"$(GO_LD_FLAGS)\" ./cmd/kubeseal\n\ndefine binary\n$(1)-static-$(2)-$(3): $(GO_FILES)\n\tGOOS=$(2) GOARCH=$(3) CGO_ENABLED=0 $(GO) build -o $$@ -installsuffix cgo $(GO_FLAGS) -ldflags \"$(GO_LD_FLAGS)\" ./cmd/$(1)\nendef\n\ndefine binaries\n$(call binary,controller,$1,$2)\n$(call binary,kubeseal,$1,$2)\nendef\n\n$(eval $(call binaries,linux,amd64))\n$(eval $(call binaries,linux,arm64))\n$(eval $(call binaries,linux,arm))\n$(eval $(call binaries,darwin,amd64))\n$(eval $(call binary,kubeseal,windows,amd64))\n\ncontroller-static: controller-static-$(GOOS)-$(GOARCH)\n\tcp $< $@\n\nkubeseal-static: kubeseal-static-$(GOOS)-$(GOARCH)\n\tcp $< $@\n\n\ndefine image\n$(1).image.$(3)-$(4): docker/$(1).Dockerfile $(1)-static-$(3)-$(4)\n\tmkdir -p dist/$(1)_$(3)_$(4)\n\tcp $(1)-static-$(3)-$(4) dist/$(1)_$(3)_$(4)/$(1)\n\t$(DOCKER) build --build-arg TARGETARCH=$(4) -t $(2)-$(3)-$(4) -f docker/$(1).Dockerfile .\n\t@echo $(2)-$(3)-$(4) >$$@.tmp\n\t@mv $$@.tmp $$@\nendef\n\ndefine images\n$(call image,controller,${CONTROLLER_IMAGE},$1,$2)\n$(call image,kubeseal,${KUBESEAL_IMAGE},$1,$2)\nendef\n\n$(eval $(call images,linux,amd64))\n$(eval $(call images,linux,arm64))\n$(eval $(call images,linux,arm))\n\n%.yaml: %.jsonnet\n\t$(KUBECFG) show -V CONTROLLER_IMAGE=$(CONTROLLER_IMAGE) -V IMAGE_PULL_POLICY=$(IMAGE_PULL_POLICY) -o yaml $< > $@.tmp\n\tmv $@.tmp $@\n\ncontroller.yaml: controller.jsonnet controller-norbac.jsonnet schema-v1alpha1.yaml kube-fixes.libsonnet\n\ncontroller-norbac.yaml: controller-norbac.jsonnet schema-v1alpha1.yaml kube-fixes.libsonnet\n\ncontroller-podmonitor.yaml: controller.jsonnet controller-norbac.jsonnet schema-v1alpha1.yaml kube-fixes.libsonnet\n\ntest:\n\t$(GOTESTSUM) $(GO_FLAGS) --junitfile report.xml --format testname -- \"-coverprofile=coverage.out\" $(GO_PACKAGES)\n\nintegrationtest: kubeseal controller\n\t# Assumes a k8s cluster exists, with controller already installed\n\t$(GINKGO) -tags 'integration' integration -- -kubeconfig $(KUBECONFIG) -kubeseal-bin $(abspath $<) -controller-bin $(abspath $(word 2,$^))\n\nvet:\n\t# known issue:\n\t# pkg/client/clientset/versioned/fake/clientset_generated.go:46: literal copies lock value from fakePtr\n\t$(GO) vet $(GO_FLAGS) -copylocks=false $(GO_PACKAGES)\n\nfmt:\n\t$(GOFMT) -s -w $(GO_FILES)\n\nlint:\n\t $(GOLANGCILINT) run --enable goimports --timeout=5m\n\nlint-gosec:\n\t $(GOSEC) -r -severity low -exclude-generated\n\nclean:\n\t$(RM) ./controller ./kubeseal\n\t$(RM) *-static*\n\t$(RM) controller*.yaml\n\t$(RM) controller.image*\n\ncheck-k8s:\n\tscripts/check-k8s\n\npush-controller: clean check-k8s controller.image.$(OS)-$(ARCH)\n\tdocker tag $(CONTROLLER_IMAGE)-$(OS)-$(ARCH) $(CONTROLLER_IMAGE)\nifeq ($(REGISTRY),docker.io)\n  echo \"Skip push: docker.io registry means minikube\"\nelse\n\tdocker push $(CONTROLLER_IMAGE)\nendif\n\napply-controller-manifests: clean check-k8s controller.yaml\n\tkubectl apply -f controller.yaml\n\tkubectl rollout status deployment sealed-secrets-controller -n kube-system\n\ncontroller-tests: test push-controller apply-controller-manifests clean integrationtest\n\n.PHONY: all kubeseal controller test clean vet fmt lint-gosec\n\n.PHONY: controllertests check-k8s push-controller apply-controller-manifests\n"
  },
  {
    "path": "README.md",
    "content": "# \"Sealed Secrets\" for Kubernetes\n\n[![](https://img.shields.io/badge/install-docs-brightgreen.svg)](#Installation)\n[![](https://img.shields.io/github/release/bitnami-labs/sealed-secrets.svg)](https://github.com/bitnami-labs/sealed-secrets/releases/latest)\n[![](https://img.shields.io/homebrew/v/kubeseal)](https://formulae.brew.sh/formula/kubeseal)\n[![Build Status](https://github.com/bitnami-labs/sealed-secrets/actions/workflows/ci.yml/badge.svg)](https://github.com/bitnami-labs/sealed-secrets/actions/workflows/ci.yml)\n[![](https://img.shields.io/github/v/release/bitnami-labs/sealed-secrets?include_prereleases&label=helm&sort=semver)](https://github.com/bitnami-labs/sealed-secrets/releases)\n[![Download Status](https://img.shields.io/docker/pulls/bitnami/sealed-secrets-controller.svg)](https://hub.docker.com/r/bitnami/sealed-secrets-controller)\n[![Go Report Card](https://goreportcard.com/badge/github.com/bitnami-labs/sealed-secrets)](https://goreportcard.com/report/github.com/bitnami-labs/sealed-secrets)\n![Downloads](https://img.shields.io/github/downloads/bitnami-labs/sealed-secrets/total.svg)\n\n**Problem:** \"I can manage all my K8s config in git, except Secrets.\"\n\n**Solution:** Encrypt your Secret into a SealedSecret, which *is* safe\nto store - even inside a public repository. The SealedSecret can be\ndecrypted only by the controller running in the target cluster and\nnobody else (not even the original author) is able to obtain the\noriginal Secret from the SealedSecret.\n\n<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->\n\n\n- [Overview](#overview)\n  - [SealedSecrets as templates for secrets](#sealedsecrets-as-templates-for-secrets)\n  - [Public key / Certificate](#public-key--certificate)\n  - [Scopes](#scopes)\n- [Installation](#installation)\n  - [Installation in Restricted Environments (No RBAC)](#installation-in-restricted-environments-no-rbac)\n  - [Controller](#controller)\n    - [Kustomize](#kustomize)\n    - [Helm Chart](#helm-chart)\n      - [Helm Chart on a restricted environment](#helm-chart-on-a-restricted-environment)\n  - [Kubeseal](#kubeseal)\n    - [Homebrew](#homebrew)\n    - [MacPorts](#macports)\n    - [Nixpkgs](#nixpkgs)\n    - [Linux](#linux)\n    - [Installation from source](#installation-from-source)\n- [Upgrade](#upgrade)\n  - [Supported Versions](#supported-versions)\n  - [Compatibility with Kubernetes versions](#compatibility-with-kubernetes-versions)\n- [Usage](#usage)\n  - [Managing existing secrets](#managing-existing-secrets)\n  - [Patching existing secrets](#patching-existing-secrets)\n  - [Seal secret which can skip set owner references](#seal-secret-which-can-skip-set-owner-references)\n  - [Update existing secrets](#update-existing-secrets)\n  - [Raw mode (experimental)](#raw-mode-experimental)\n  - [Validate a Sealed Secret](#validate-a-sealed-secret)\n- [Secret Rotation](#secret-rotation)\n  - [Sealing key renewal](#sealing-key-renewal)\n  - [Key registry init priority order](#key-registry-init-priority-order)\n  - [User secret rotation](#user-secret-rotation)\n  - [Early key renewal](#early-key-renewal)\n  - [Common misconceptions about key renewal](#common-misconceptions-about-key-renewal)\n  - [Manual key management (advanced)](#manual-key-management-advanced)\n  - [Re-encryption (advanced)](#re-encryption-advanced)\n- [Details (advanced)](#details-advanced)\n  - [Crypto](#crypto)\n- [Developing](#developing)\n- [FAQ](#faq)\n  - [Can I encrypt multiple secrets at once, in one YAML / JSON file?](#can-i-encrypt-multiple-secrets-at-once-in-one-yaml--json-file)\n  - [Will you still be able to decrypt if you no longer have access to your cluster?](#will-you-still-be-able-to-decrypt-if-you-no-longer-have-access-to-your-cluster)\n  - [How can I do a backup of my SealedSecrets?](#how-can-i-do-a-backup-of-my-sealedsecrets)\n  - [Can I decrypt my secrets offline with a backup key?](#can-i-decrypt-my-secrets-offline-with-a-backup-key)\n  - [What flags are available for kubeseal?](#what-flags-are-available-for-kubeseal)\n  - [How do I update parts of JSON/YAML/TOML/.. file encrypted with sealed secrets?](#how-do-i-update-parts-of-jsonyamltoml-file-encrypted-with-sealed-secrets)\n  - [Can I bring my own (pre-generated) certificates?](#can-i-bring-my-own-pre-generated-certificates)\n  - [How to use kubeseal if the controller is not running within the `kube-system` namespace?](#how-to-use-kubeseal-if-the-controller-is-not-running-within-the-kube-system-namespace)\n  - [How to verify the images?](#how-to-verify-the-images)\n  - [How to use one controller for a subset of namespaces](#how-to-use-one-controller-for-a-subset-of-namespaces)\n  - [Can I configure the Controller unseal retries?](#can-i-configure-the-controller-unseal-retries)\n  - [How to manage SealedSecrets across the cluster or specific namespaces?](#how-to-manage-sealedsecrets-across-the-cluster-or-specific-namespaces)\n- [Community](#community)\n  - [Related projects](#related-projects)\n\n<!-- END doctoc generated TOC please keep comment here to allow auto update -->\n\n## Overview\n\nSealed Secrets is composed of two parts:\n\n- A cluster-side controller / operator\n- A client-side utility: `kubeseal`\n\nThe `kubeseal` utility uses asymmetric crypto to encrypt secrets that only the controller can decrypt.\n\nThese encrypted secrets are encoded in a `SealedSecret` resource, which you can see as a recipe for creating\na secret. Here is how it looks:\n\n```yaml\napiVersion: bitnami.com/v1alpha1\nkind: SealedSecret\nmetadata:\n  name: mysecret\n  namespace: mynamespace\nspec:\n  encryptedData:\n    foo: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEq.....\n```\n\nOnce unsealed this will produce a secret equivalent to this:\n\n```yaml\napiVersion: v1\nkind: Secret\nmetadata:\n  name: mysecret\n  namespace: mynamespace\ndata:\n  foo: YmFy  # <- base64 encoded \"bar\"\n```\n\nThis normal [kubernetes secret](https://kubernetes.io/docs/concepts/configuration/secret/) will appear in the cluster\nafter a few seconds you can use it as you would use any secret that you would have created directly (e.g. reference it from a `Pod`).\n\nJump to the [Installation](#installation) section to get up and running.\n\nThe [Usage](#usage) section explores in more detail how you craft `SealedSecret` resources.\n\n### SealedSecrets as templates for secrets\n\nThe previous example only focused on the encrypted secret items themselves, but the relationship between a `SealedSecret` custom resource and the `Secret` it unseals into is similar in many ways (but not in all of them) to the familiar `Deployment` vs `Pod`.\n\nIn particular, the annotations and labels of a `SealedSecret` resource are not the same as the annotations of the `Secret` that gets generated out of it.\n\nTo capture this distinction, the `SealedSecret` object has a `template` section which encodes all the fields you want the controller to put in the unsealed `Secret`.\n\nThe [Sprig function library](https://masterminds.github.io/sprig/) is available (except for `env`, `expandenv` and `getHostByName`) in addition to the default Go Text Template functions.\n\nThe `metadata` block is copied as is (the `ownerReference` field will be updated [unless disabled](#seal-secret-which-can-skip-set-owner-references)).\n\nOther secret fields are handled individually. The `type` and `immutable` fields are copied, and the `data` field can be used to [template complex values](docs/examples/config-template) on the `Secret`. All other fields are currently ignored.\n\n```yaml\napiVersion: bitnami.com/v1alpha1\nkind: SealedSecret\nmetadata:\n  name: mysecret\n  namespace: mynamespace\n  annotations:\n    \"kubectl.kubernetes.io/last-applied-configuration\": ....\nspec:\n  encryptedData:\n    .dockerconfigjson: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEq.....\n  template:\n    type: kubernetes.io/dockerconfigjson\n    immutable: true\n    # this is an example of labels and annotations that will be added to the output secret\n    metadata:\n      labels:\n        \"jenkins.io/credentials-type\": usernamePassword\n      annotations:\n        \"jenkins.io/credentials-description\": credentials from Kubernetes\n```\n\nThe controller would unseal that into something like:\n\n```yaml\napiVersion: v1\nkind: Secret\nmetadata:\n  name: mysecret\n  namespace: mynamespace\n  labels:\n    \"jenkins.io/credentials-type\": usernamePassword\n  annotations:\n    \"jenkins.io/credentials-description\": credentials from Kubernetes\n  ownerReferences:\n  - apiVersion: bitnami.com/v1alpha1\n    controller: true\n    kind: SealedSecret\n    name: mysecret\n    uid: 5caff6a0-c9ac-11e9-881e-42010aac003e\ntype: kubernetes.io/dockerconfigjson\nimmutable: true\ndata:\n  .dockerconfigjson: ewogICJjcmVk...\n```\n\nAs you can see, the generated `Secret` resource is a \"dependent object\" of the `SealedSecret` and as such\nit will be updated and deleted whenever the `SealedSecret` object gets updated or deleted.\n\n### Public key / Certificate\n\nThe key certificate (public key portion) is used for sealing secrets,\nand needs to be available wherever `kubeseal` is going to be\nused. The certificate is not secret information, although you need to\nensure you are using the correct one.\n\n`kubeseal` will fetch the certificate from the controller at runtime\n(requires secure access to the Kubernetes API server), which is\nconvenient for interactive use, but it's known to be brittle when users\nhave clusters with special configurations such as [private GKE clusters](docs/GKE.md#private-gke-clusters) that have\nfirewalls between control plane and nodes.\n\nAn alternative workflow\nis to store the certificate somewhere (e.g. local disk) with\n`kubeseal --fetch-cert >mycert.pem`,\nand use it offline with `kubeseal --cert mycert.pem`.\nThe certificate is also printed to the controller log on startup.\n\nSince v0.9.x certificates get automatically renewed every 30 days. It's good practice that you and your team\nupdate your offline certificate periodically. To help you with that, since v0.9.2 `kubeseal` accepts URLs too. You can set up your internal automation to publish certificates somewhere you trust.\n\n```bash\nkubeseal --cert https://your.intranet.company.com/sealed-secrets/your-cluster.cert\n```\n\nIt also recognizes the `SEALED_SECRETS_CERT` env var. (pro-tip: see also [direnv](https://github.com/direnv/direnv)).\n\n> **NOTE**: we are working on providing key management mechanisms that offload the encryption to HSM based modules or managed cloud crypto solutions such as KMS.\n\n### Scopes\n\nSealedSecrets are from the POV of an end user a \"write only\" device.\n\nThe idea is that the SealedSecret can be decrypted only by the controller running in the target cluster and\nnobody else (not even the original author) is able to obtain the original Secret from the SealedSecret.\n\nThe user may or may not have direct access to the target cluster.\nMore specifically, the user might or might not have access to the Secret unsealed by the controller.\n\nThere are many ways to configure RBAC on k8s, but it's quite common to forbid low-privilege users\nfrom reading Secrets. It's also common to give users one or more namespaces where they have higher privileges,\nwhich would allow them to create and read secrets (and/or create deployments that can reference those secrets).\n\nEncrypted `SealedSecret` resources are designed to be safe to be looked at without gaining any knowledge about the secrets it conceals. This implies that we cannot allow users to read a SealedSecret meant for a namespace they wouldn't have access to\nand just push a copy of it in a namespace where they can read secrets from.\n\nSealed-secrets thus behaves *as if* each namespace had its own independent encryption key and thus once you\nseal a secret for a namespace, it cannot be moved in another namespace and decrypted there.\n\nWe don't technically use an independent private key for each namespace, but instead we *include* the namespace name\nduring the encryption process, effectively achieving the same result.\n\nFurthermore, namespaces are not the only level at which RBAC configurations can decide who can see which secret. In fact, it's possible that users can access a secret called `foo` in a given namespace but not any other secret in the same namespace. We cannot thus by default let users freely rename `SealedSecret` resources otherwise a malicious user would be able to decrypt any SealedSecret for that namespace by just renaming it to overwrite the one secret user does have access to. We use the same mechanism used to include the namespace in the encryption key to also include the secret name.\n\nThat said, there are many scenarios where you might not care about this level of protection. For example, the only people who have access to your clusters are either admins or they cannot read any `Secret` resource at all. You might have a use case for moving a sealed secret to other namespaces (e.g. you might not know the namespace name upfront), or you might not know the name of the secret (e.g. it could contain a unique suffix based on the hash of the contents etc).\n\nThese are the possible scopes:\n\n- `strict` (default): the secret must be sealed with exactly the same *name* and *namespace*. These attributes become *part of the encrypted data* and thus changing name and/or namespace would lead to \"decryption error\".\n- `namespace-wide`: you can freely *rename* the sealed secret within a given namespace.\n- `cluster-wide`: the secret can be unsealed in *any* namespace and can be given *any* name.\n\nIn contrast to the restrictions of *name* and *namespace*, secret *items* (i.e. JSON object keys like `spec.encryptedData.my-key`) can be renamed at will without losing the ability to decrypt the sealed secret.\n\nThe scope is selected with the `--scope` flag:\n\n```bash\nkubeseal --scope cluster-wide <secret.yaml >sealed-secret.json\n```\n\nIt's also possible to request a scope via annotations in the input secret you pass to `kubeseal`:\n\n- `sealedsecrets.bitnami.com/namespace-wide: \"true\"` -> for `namespace-wide`\n- `sealedsecrets.bitnami.com/cluster-wide: \"true\"` -> for `cluster-wide`\n\nThe lack of any of such annotations means `strict` mode. If both are set, `cluster-wide` takes precedence.\n\n> NOTE: Next release will consolidate this into a single `sealedsecrets.bitnami.com/scope` annotation.\n\n## Installation\n\nSee https://github.com/bitnami-labs/sealed-secrets/releases for the latest release and detailed installation instructions.\n\nCloud platform specific notes and instructions:\n\n- [GKE](docs/GKE.md)\n\n### Installation in Restricted Environments (No RBAC)\n\nIn environments where you lack permissions to create cluster-wide RBAC resources (like `ClusterRoles`), you can use the **`controller-norbac.yaml`** manifest available on the Releases page.\n\nThis version is a minimal deployment that includes only the **Deployment**, **Service**, and **CustomResourceDefinition**. It intentionally omits `ServiceAccount`, `ClusterRole`, and `ClusterRoleBinding`.\n\n**Requirements:**\n1. A cluster administrator must have already installed the SealedSecret CRDs.\n2. You must have an allocated Service Account to run the deployment\n\n### Controller\n\nOnce you deploy the manifest it will create the `SealedSecret` resource\nand install the controller into `kube-system` namespace, create a service\naccount and necessary RBAC roles.\n\nAfter a few moments, the controller will start, generate a key pair,\nand be ready for operation. If it does not, check the controller logs.\n\n#### Kustomize\n\nThe official controller manifest installation mechanism is just a YAML file.\n\nIn some cases you might need to apply your own customizations, like set a custom namespace or set some env variables.\n\n`kubectl` has native support for that, see [kustomize](https://kustomize.io/).\n\n#### Helm Chart\n\nThe Sealed Secrets helm chart is now officially supported and hosted in this GitHub repo.\n\n```bash\nhelm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets\n```\n\n> NOTE: The versioning scheme of the helm chart differs from the versioning scheme of the sealed secrets project itself.\n\nOriginally the helm chart was maintained by the community and the first version adopted a major version of 1 while the\nsealed secrets project itself is still at major 0.\nThis is ok because the version of the helm chart itself is not meant to be necessarily the version of the app itself.\nHowever this is confusing, so our current versioning rule is:\n\n1. The `SealedSecret` controller version scheme: 0.X.Y\n2. The helm chart version scheme: 1.X.Y-rZ\n\nThere can be thus multiple revisions of the helm chart, with fixes that apply only to the helm chart without\naffecting the static YAML manifests or the controller image itself.\n\n> NOTE: The helm chart by default installs the controller with the name `sealed-secrets`, while the `kubeseal` command line interface (CLI) tries to access the controller with the name `sealed-secrets-controller`. You can explicitly pass `--controller-name` to the CLI:\n\n```bash\nkubeseal --controller-name sealed-secrets <args>\n```\n\nAlternatively, you can set `fullnameOverride` when installing the chart to override the name. Note also that `kubeseal` assumes that the controller is installed within the `kube-system` namespace by default. So if you want to use the `kubeseal` CLI without having to pass the expected controller name and namespace you should install the Helm Chart like this:\n\n```bash\nhelm install sealed-secrets -n kube-system --set-string fullnameOverride=sealed-secrets-controller sealed-secrets/sealed-secrets\n```\n\n##### Helm Chart on a restricted environment\n\nIn some companies you might be given access only to a single namespace, not a full cluster.\n\nOne of the most restrictive environments you can encounter is:\n- A `namespace` was allocated to you with some `service account`.\n- You do not have access to the rest of the cluster, not even cluster CRDs.\n- You may not even be able to create further service accounts or roles in your namespace.\n- You are required to include resource limits in all your deployments.\n\nEven with these restrictions you can still install the sealed secrets Helm Chart, there is only one pre-requisite:\n- *The cluster must already have the sealed secrets CRDs installed*.\n\nOnce your admins installed the CRDs, if they were not there already, you can install the chart by preparing a YAML config file such as this:\n\n```shell\nserviceAccount:\n  create: false\n  name: {allocated-service-account}\nrbac:\n  create: false\n  clusterRole: false\nresources:\n  limits:\n    cpu: 150m\n    memory: 256Mi\n```\n\nNote that:\n- No service accounts are created, instead the one allocated to you will be used.\n  - `{allocated-service-account}` is the name of the `service account` you were allocated on the cluster.\n- No RBAC roles are created neither in the namespace nor the cluster.\n- Resource limits must be specified.\n  - The limits are samples that should work, but you might want to review them in your particular setup.\n\nOnce that file is ready, if you named it `config.yaml` you now can install the sealed secrets Helm Chart like this:\n\n```shell\nhelm install sealed-secrets -n {allocated-namespace} sealed-secrets/sealed-secrets --skip-crds -f config.yaml\n```\n\nWhere `{allocated-namespace}` is the name of the `namespace` you were allocated in the cluster.\n\n### Kubeseal\n\n#### Homebrew\n\nThe `kubeseal` client is also available on [homebrew](https://formulae.brew.sh/formula/kubeseal):\n\n```bash\nbrew install kubeseal\n```\n\n#### MacPorts\n\nThe `kubeseal` client is also available on [MacPorts](https://ports.macports.org/port/kubeseal/summary):\n\n```bash\nport install kubeseal\n```\n\n#### Nixpkgs\n\nThe `kubeseal` client is also available on [Nixpkgs](https://search.nixos.org/packages?channel=unstable&show=kubeseal&from=0&size=50&sort=relevance&type=packages&query=kubeseal): (**DISCLAIMER**: Not maintained by bitnami-labs)\n\n```bash\nnix-env -iA nixpkgs.kubeseal\n```\n\n#### Linux\n\nThe `kubeseal` client can be installed on Linux, using the below commands:\n\n```bash\nKUBESEAL_VERSION='' # Set this to, for example, KUBESEAL_VERSION='0.23.0'\ncurl -OL \"https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION:?}/kubeseal-${KUBESEAL_VERSION:?}-linux-amd64.tar.gz\"\ntar -xvzf kubeseal-${KUBESEAL_VERSION:?}-linux-amd64.tar.gz kubeseal\nsudo install -m 755 kubeseal /usr/local/bin/kubeseal\n```\n\nIf you have `curl` and `jq` installed on your machine, you can get the version dynamically this way. This can be useful for environments used in automation and such.\n\n```\n# Fetch the latest sealed-secrets version using GitHub API\nKUBESEAL_VERSION=$(curl -s https://api.github.com/repos/bitnami-labs/sealed-secrets/tags | jq -r '.[0].name' | cut -c 2-)\n\n# Check if the version was fetched successfully\nif [ -z \"$KUBESEAL_VERSION\" ]; then\n    echo \"Failed to fetch the latest KUBESEAL_VERSION\"\n    exit 1\nfi\n\ncurl -OL \"https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION}/kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz\"\ntar -xvzf kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz kubeseal\nsudo install -m 755 kubeseal /usr/local/bin/kubeseal\n```\n\nwhere `KUBESEAL_VERSION` is the [version tag](https://github.com/bitnami-labs/sealed-secrets/tags) of the kubeseal release you want to use. For example: `v0.18.0`.\n\n#### Installation from source\n\nIf you just want the latest client tool, it can be installed into\n`$GOPATH/bin` with:\n\n```bash\ngo install github.com/bitnami-labs/sealed-secrets/cmd/kubeseal@main\n```\n\nYou can specify a release tag or a commit SHA instead of `main`.\n\nThe `go install` command will place the `kubeseal` binary at `$GOPATH/bin`:\n\n```bash\n$(go env GOPATH)/bin/kubeseal\n```\n\n## Upgrade\n\nDon't forget to check the [release notes](RELEASE-NOTES.md) for guidance about\npossible breaking changes when you upgrade the client tool\nand/or the controller.\n\n### Supported Versions\nCurrently, only the latest version of Sealed Secrets is supported for production environments.\n\n### Compatibility with Kubernetes versions\nThe Sealed Secrets controller ensures compatibility with different versions of Kubernetes by relying on a stable Kubernetes API. Typically, Kubernetes versions above 1.16 are considered compatible. However, we officially support the [currently recommended Kubernetes versions](https://kubernetes.io/releases/). Additionally, versions above 1.24 undergo thorough verification through our CI process with every release.\n\n## Usage\n\n```bash\n# Create a json/yaml-encoded Secret somehow:\n# (note use of `--dry-run` - this is just a local file!)\necho -n bar | kubectl create secret generic mysecret --dry-run=client --from-file=foo=/dev/stdin -o json >mysecret.json\n\n# This is the important bit:\nkubeseal -f mysecret.json -w mysealedsecret.json\n\n# At this point mysealedsecret.json is safe to upload to Github,\n# post on Twitter, etc.\n\n# Eventually:\nkubectl create -f mysealedsecret.json\n\n# Profit!\nkubectl get secret mysecret\n```\n\nNote the `SealedSecret` and `Secret` must have **the same namespace and\nname**. This is a feature to prevent other users on the same cluster\nfrom re-using your sealed secrets. See the [Scopes](#scopes) section for more info.\n\n`kubeseal` reads the namespace from the input secret, accepts an explicit `--namespace` argument, and uses\nthe `kubectl` default namespace (in that order). Any labels,\nannotations, etc on the original `Secret` are preserved, but not\nautomatically reflected in the `SealedSecret`.\n\nBy design, this scheme *does not authenticate the user*. In other\nwords, *anyone* can create a `SealedSecret` containing any `Secret`\nthey like (provided the namespace/name matches). It is up to your\nexisting config management workflow, cluster RBAC rules, etc to ensure\nthat only the intended `SealedSecret` is uploaded to the cluster. The\nonly change from existing Kubernetes is that the *contents* of the\n`Secret` are now hidden while outside the cluster.\n\n### Managing existing secrets\n\nIf you want the Sealed Secrets controller to manage an existing `Secret`, you can annotate your `Secret` with the `sealedsecrets.bitnami.com/managed: \"true\"` annotation. The existing `Secret` will be overwritten when unsealing a `SealedSecret` with the same name and namespace, and the `SealedSecret` will take ownership of the `Secret` (so that when the `SealedSecret` is deleted the `Secret` will also be deleted).\n\n### Patching existing secrets\n\n> New in v0.23.0\n\nThere are some use cases in which you don't want to replace the whole `Secret` but just add or modify some keys from the existing `Secret`. For this, you can annotate your `Secret` with `sealedsecrets.bitnami.com/patch: \"true\"`. Using this annotation will make sure that secret keys, labels and annotations in the `Secret` that are not present in the `SealedSecret` won't be deleted, and those present in the `SealedSecret` will be added to the `Secret` (secret keys, labels and annotations that exist both in the `Secret` and the `SealedSecret` will be modified by the `SealedSecret`).\n\nThis annotation does not make the `SealedSecret` take ownership of the `Secret`. You can add both the `patch` and `managed` annotations to obtain the patching behavior while also taking ownership of the `Secret`.\n\n### Seal secret which can skip set owner references\n\nIf you want `SealedSecret` and the `Secret` to be independent, which mean when you delete the `SealedSecret` the `Secret` won't disappear with it, then you have to annotate that Secret with the annotation `sealedsecrets.bitnami.com/skip-set-owner-references: \"true\"` ahead of applying the Usage steps. You still may also add `sealedsecrets.bitnami.com/managed: \"true\"` to your `Secret` so that your secret will be updated when `SealedSecret` is updated.\n\n### Update existing secrets\n\nIf you want to add or update existing sealed secrets without having the cleartext for the other items,\nyou can just copy&paste the new encrypted data items and merge it into an existing sealed secret.\n\nYou must take care of sealing the updated items with a compatible name and namespace (see note about scopes above).\n\nYou can use the `--merge-into` command to update an existing sealed secrets if you don't want to copy&paste:\n\n```bash\necho -n bar | kubectl create secret generic mysecret --dry-run=client --from-file=foo=/dev/stdin -o json \\\n  | kubeseal > mysealedsecret.json\necho -n baz | kubectl create secret generic mysecret --dry-run=client --from-file=bar=/dev/stdin -o json \\\n  | kubeseal --merge-into mysealedsecret.json\n```\n\n### Raw mode (experimental)\n\nCreating temporary Secret with the `kubectl` command, only to throw it away once piped to `kubeseal` can\nbe a quite unfriendly user experience. We're working on an overhaul of the CLI experience. In the meantime,\nwe offer an alternative mode where kubeseal only cares about encrypting a value to stdout, and it's your responsibility to put it inside a `SealedSecret` resource (not unlike any of the other k8s resources).\n\nIt can also be useful as a building block for editor/IDE integrations.\n\nThe downside is that you have to be careful to be consistent with the sealing scope, the namespace and the name.\n\nSee [Scopes](#scopes)\n\n`strict` scope (default):\n\n```console\n$ echo -n foo | kubeseal --raw --namespace bar --name mysecret\nAgBChHUWLMx...\n```\n\n`namespace-wide` scope:\n\n```console\n$ echo -n foo | kubeseal --raw --namespace bar --scope namespace-wide\nAgAbbFNkM54...\n```\nInclude the `sealedsecrets.bitnami.com/namespace-wide` annotation in the `SealedSecret`\n```yaml\nmetadata:\n  annotations:\n    sealedsecrets.bitnami.com/namespace-wide: \"true\"\n```\n\n`cluster-wide` scope:\n\n```console\n$ echo -n foo | kubeseal --raw --scope cluster-wide\nAgAjLKpIYV+...\n```\nInclude the `sealedsecrets.bitnami.com/cluster-wide` annotation in the `SealedSecret`\n```yaml\nmetadata:\n  annotations:\n    sealedsecrets.bitnami.com/cluster-wide: \"true\"\n```\n\n### Validate a Sealed Secret\n\nIf you want to validate an existing sealed secret, `kubeseal` has the flag `--validate` to help you.\n\nGiving a file named `sealed-secrets.yaml` containing the following sealed secret:\n\n```yaml\napiVersion: bitnami.com/v1alpha1\nkind: SealedSecret\nmetadata:\n  name: mysecret\n  namespace: mynamespace\nspec:\n  encryptedData:\n    foo: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEq.....\n```\n\nYou can validate if the sealed secret was properly created or not:\n\n```console\n$ cat sealed-secrets.yaml | kubeseal --validate\n```\n\nIn case of an invalid sealed secret, `kubeseal` will show:\n\n```console\n$ cat sealed-secrets.yaml | kubeseal --validate\nerror: unable to decrypt sealed secret\n```\n\n## Secret Rotation\n\nYou should always rotate your secrets. But since your secrets are encrypted with another secret,\nyou need to understand how these two layers relate to take the right decisions.\n\nTL;DR:\n\n> If a *sealing* private key is compromised, you need to follow the instructions below in \"Early key renewal\"\n> section before rotating any of your actual secret values.\n>\n> SealedSecret key renewal and re-encryption features are **not a substitute** for periodical rotation of your actual secret values.\n\n### Sealing key renewal\n\nSealing keys are automatically renewed every 30 days. Which means a new sealing key is created and appended to the set of active sealing keys the controller can use to unseal `SealedSecret` resources.\n\nThe most recently created sealing key is the one used to seal new secrets when you use `kubeseal` and it's the one whose certificate is downloaded when you use `kubeseal --fetch-cert`.\n\nThe renewal time of 30 days is a reasonable default, but it can be tweaked as needed\nwith the `--key-renew-period=<value>` flag for the command in the pod template of the `SealedSecret` controller. The `value` field can be given as golang\nduration flag (eg: `720h30m`). Assuming that you've installed Sealed Secrets into the `kube-system` namespace, use the following command to edit the Deployment controller, and add the `--key-renew-period` parameter. Once you close your text editor, and the Deployment controller has been modified, a new Pod will be automatically created to replace the old Pod.\n\n```\nkubectl edit deployment/sealed-secrets-controller --namespace=kube-system\n```\n\nA value of `0` will deactivate automatic key renewal. Of course, you may have a valid use case for deactivating automatic sealing key renewal but experience has shown that new users often tend to jump to conclusions that they want control over key renewal, before fully understanding how sealed secrets work. Read more about this in the [common misconceptions](#common-misconceptions-about-key-renewal) section below.\n\n> Unfortunately, you cannot use e.g. \"d\" as a unit for days because that's not supported by the Go stdlib. Instead of hitting your face with a palm, take this as an opportunity to meditate on the [falsehoods programmers believe about time](https://infiniteundo.com/post/25326999628/falsehoods-programmers-believe-about-time).\n\nA common misunderstanding is that key renewal is often thought of as a form of key rotation, where the old key is not only obsolete but actually bad and that you thus want to get rid of it.\nIt doesn't help that this feature has been historically called \"key rotation\", which can add to the confusion.\n\nSealed secrets are not automatically rotated and old keys are not deleted\nwhen new keys are generated. Old `SealedSecret` resources can be still decrypted (that's because old sealing keys are not deleted).\n\n### Key registry init priority order\n\nWhen the controller starts, it will initialize the key registry. The most recent key is used to seal secrets. By default, this certificate is chosen based on the NotBefore attribute of the certificate. If you want to change the priority order of the keys in the registry, you can use the `--key-order-priority` flag. \n\nThe `--key-order-priority` flag accepts the following values:\n- `CertNotBefore`: (default) The key registry will be ordered based on the NotBefore attribute of the key certificate.\n- `SecretCreationTimestamp`: The key registry will be ordered based on the creation timestamp of the secret.\n\nThis flag influences the public key used to encrypt secrets and the certificate retrieved by `kubeseal --fetch-cert`. \n\n\n\n### User secret rotation\n\nThe *sealing key* renewal and SealedSecret rotation are **not a substitute** for rotating your actual secrets.\n\nA core value proposition of this tool is:\n\n> Encrypt your Secret into a SealedSecret, which *is* safe to store - even inside a public repository.\n\nIf you store anything in a version control storage, and in a public one in particular, you must assume\nyou cannot ever delete that information.\n\n*If* a sealing key somehow leaks out of the cluster you must consider all your `SealedSecret` resources\nencrypted with that key as compromised. No amount of sealing key rotation in the cluster or even re-encryption of existing SealedSecrets files can change that.\n\nThe best practice is to periodically rotate all your actual secrets (e.g. change the password) **and** craft new\n`SealedSecret` resources with those new secrets.\n\nBut if the `SealedSecret` controller was not renewing the *sealing key* that rotation would be moot,\nsince the attacker could just decrypt the new secrets as well. Thus, you need to do both: periodically renew the sealing key and rotate your actual secrets!\n\n### Early key renewal\n\nIf you know or suspect a *sealing key* has been compromised you should renew the key ASAP before you\nstart sealing your new rotated secrets, otherwise you'll be giving attackers access to your new secrets as well.\n\nA key can be generated early by passing the current timestamp to the controller into a flag called `--key-cutoff-time` or an env var called `SEALED_SECRETS_KEY_CUTOFF_TIME`. The expected format is RFC1123, you can generate it with the `date -R` unix command.\n\n### Common misconceptions about key renewal\n\nSealed secrets sealing keys are not access control keys (e.g. a password). They are more like the GPG key you might use to read encrypted mail sent to you. Let's continue with the email analogy for a bit:\n\nImagine you have reasons to believe your private GPG key might have been compromised. You'd have more to lose than to gain if the first thing you do is just delete your private key. All the previous emails sent with that key are no longer accessible to you (unless you have a decrypted copy of those emails), nor are new emails sent by your friends whom you have not yet managed to tell to use the new key.\n\nSure, the content of those encrypted emails is not secure, as an attacker might now be able to decrypt them, but what's done is done. Your sudden loss of the ability to read those emails surely doesn't undo the damage. If anything, it's worse because you no longer know for sure what secret the attacker got to know. What you really want to do is to make sure that your friend stops using your old key and that from now on all further communication is encrypted with a new key pair (i.e. your friend must know about that new key).\n\nThe same logic applies to SealedSecrets. The ultimate goal is to secure your actual \"user\" secrets. The \"sealing\" secrets are just a mechanism, an \"envelope\". If a secret is leaked there is no going back, what's done is done.\n\nYou first need to ensure that new secrets don't get encrypted with that old compromised key (in the email analogy above that's: create a new key pair and give all your friends your new public key).\n\nThe second logical step is to neutralize the damage, which depends on the nature of the secret. A simple example is a database password: if you accidentally leak your database password, the thing you're supposed to do is simply to change your database password (on the database; and revoke the old one!) *and* update the `SealedSecret` resource with the new password (i.e. running `kubeseal` again).\n\nBoth steps are described in the previous sections, albeit in a less verbose way. There is no shame in reading them again, now that you have a more in-depth grasp of the underlying rationale.\n\n### Manual key management (advanced)\n\nThe `SealedSecret` controller and the associated workflow are designed to keep old sealing keys around and periodically add new ones. You should not delete old keys unless you know what you're doing.\n\nThat said, if you want you can manually manage (create, move, delete) *sealing keys*. They are just normal k8s secrets living in the same namespace where the `SealedSecret` controller lives (usually `kube-system`, but it's configurable).\n\nThere are advanced use cases that you can address by creative management of the sealing keys.\nFor example, you can share the same sealing key among a few clusters so that you can apply exactly the same sealed secret in multiple clusters.\nSince sealing keys are just normal k8s secrets you can even use sealed secrets themselves and use a GitOps workflow to manage your sealing keys (useful when you want to share the same key among different clusters)!\n\nLabeling a *sealing key* secret with anything other than `active` effectively deletes\nthe key from the `SealedSecret` controller, but it is still available in k8s for\nmanual encryption/decryption if need be.\n\n**NOTE** `SealedSecret` controller currently does not automatically pick up manually created, deleted or relabeled sealing keys. An admin must restart the controller before the effect will apply.\n\n### Re-encryption (advanced)\n\nBefore you can get rid of some old sealing keys you need to re-encrypt your SealedSecrets with the latest private key.\n\n```bash\nkubeseal --re-encrypt <my_sealed_secret.json >tmp.json \\\n  && mv tmp.json my_sealed_secret.json\n```\n\nThe invocation above will produce a new sealed secret file freshly encrypted with\nthe latest key, without making the secrets leave the cluster to the client. You can then save that file\nin your version control system (`kubeseal --re-encrypt` doesn't update the in-cluster object).\n\nCurrently, old keys are not garbage collected automatically.\n\nIt's a good idea to periodically re-encrypt your SealedSecrets. But as mentioned above, don't lull yourself in a false sense of security: you must assume the old version of the `SealedSecret` resource (the one encrypted with a key you think of as dead) is still potentially around and accessible to attackers. I.e. re-encryption is not a substitute for periodically rotating your actual secrets.\n\n## Details (advanced)\n\nThis controller adds a new `SealedSecret` custom resource. The\ninteresting part of a `SealedSecret` is a base64-encoded\nasymmetrically encrypted `Secret`.\n\nThe controller maintains a set of private/public key pairs as kubernetes\nsecrets. Keys are labeled with `sealedsecrets.bitnami.com/sealed-secrets-key`\nand identified in the label as either `active` or `compromised`. On startup,\nThe sealed secrets controller will...\n\n1. Search for these keys and add them to its local store if they are\nlabeled as active.\n2. Create a new key\n3. Start the key rotation cycle\n\n### Crypto\n\nMore details about crypto can be found [here](docs/developer/crypto.md).\n\n## Developing\n\nDeveloping guidelines can be found [in the Developer Guide](docs/developer/README.md).\n\n## FAQ\n\n### Can I encrypt multiple secrets at once, in one YAML / JSON file?\n\nYes, you can! Drop as many secrets as you like in one file. Make sure to separate them via `---` for YAML and as extra, single objects in JSON.\n\n### Will you still be able to decrypt if you no longer have access to your cluster?\n\nNo, the private keys are only stored in the Secret managed by the controller (unless you have some other backup of your k8s objects). There are no backdoors - without that private key used to encrypt a given SealedSecrets, you can't decrypt it. If you can't get to the Secrets with the encryption keys, and you also can't get to the decrypted versions of your Secrets live in the cluster, then you will need to regenerate new passwords for everything, seal them again with a new sealing key, etc.\n\n### How can I do a backup of my SealedSecrets?\n\nIf you do want to make a backup of the encryption private keys, it's easy to do from an account with suitable access:\n\n```bash\nkubectl get secret -n kube-system -l sealedsecrets.bitnami.com/sealed-secrets-key -o yaml >main.key\n\necho \"---\" >> main.key\nkubectl get secret -n kube-system sealed-secrets-key -o yaml >>main.key\n```\n\n> NOTE: You need the second statement only if you ever installed sealed-secrets older than version 0.9.x on your cluster.\n\n> NOTE: This file will contain the controller's public + private keys and should be kept omg-safe!\n\n> NOTE: After sealing key renewal you should recreate your backup. Otherwise, your backup won't be able to decrypt new sealed secrets.\n\nTo restore from a backup after some disaster, just put that secrets back before starting the controller - or if the controller was already started, replace the newly-created secrets and restart the controller:\n\n* For Helm deployment:\n    ```bash\n    kubectl apply -f main.key\n    kubectl delete pod -n kube-system -l app.kubernetes.io/name=sealed-secrets\n    ```\n\n* For deployment via `controller.yaml` manifest\n    ```bash\n    kubectl apply -f main.key\n    kubectl delete pod -n kube-system -l name=sealed-secrets-controller\n    ```\n\n### Can I decrypt my secrets offline with a backup key?\n\nWhile treating sealed-secrets as long term storage system for secrets is not the recommended use case, some people\ndo have a legitimate requirement for being able to recover secrets when the k8s cluster is down and restoring a backup into a new `SealedSecret` controller deployment is not practical.\n\nIf you have backed up one or more of your private keys (see previous question), you can use the `kubeseal --recovery-unseal --recovery-private-key file1.key,file2.key,...` command to decrypt a sealed secrets file.\n\n### What flags are available for kubeseal?\n\nYou can check the flags available using `kubeseal --help`.\n\n### How do I update parts of JSON/YAML/TOML/.. file encrypted with sealed secrets?\n\nA kubernetes `Secret` resource contains multiple items, basically a flat map of key/value pairs.\nSealedSecrets operate at that level, and does not care what you put in the values. In other words\nit cannot make sense of any structured configuration file you might have put in a secret and thus\ncannot help you update individual fields in it.\n\nSince this is a common problem, especially when dealing with legacy applications, we do offer an [example](docs/examples/config-template) of a possible workaround.\n\n### Can I bring my own (pre-generated) certificates?\n\nYes, you can provide the controller with your own certificates, and it will consume them.\nPlease check [here](docs/bring-your-own-certificates.md) for a workaround.\n\n### How to use kubeseal if the controller is not running within the `kube-system` namespace?\n\nIf you installed the controller in a different namespace than the default `kube-system`, you need to provide this namespace\nto the `kubeseal` commandline tool. There are two options:\n\n1. You can specify the namespace via the command line option `--controller-namespace <namespace>`:\n\n  ```bash\nkubeseal --controller-namespace sealed-secrets <mysecret.json >mysealedsecret.json\n```\n\n2. Via the environment variable `SEALED_SECRETS_CONTROLLER_NAMESPACE`:\n\n  ```bash\nexport SEALED_SECRETS_CONTROLLER_NAMESPACE=sealed-secrets\nkubeseal <mysecret.json >mysealedsecret.json\n```\n\n### How to verify the images?\n\nOur images are being signed using [cosign](https://github.com/sigstore/cosign). The signatures have been saved in our [GitHub Container Registry](https://ghcr.io/bitnami-labs/sealed-secrets-controller/signs).\n\n> Images up to and including v0.20.2 were signed using Cosign v1. Newer images are signed with Cosign v2.\n\nIt is pretty simple to verify the images:\n\n```bash\n# export the COSIGN_VARIABLE setting up the GitHub container registry signs path\nexport COSIGN_REPOSITORY=ghcr.io/bitnami-labs/sealed-secrets-controller/signs\n\n# verify the image uploaded in GHCR\ncosign verify --key .github/workflows/cosign.pub ghcr.io/bitnami-labs/sealed-secrets-controller:latest\n\n# verify the image uploaded in Dockerhub\ncosign verify --key .github/workflows/cosign.pub docker.io/bitnami/sealed-secrets-controller:latest\n```\n\n### How to use one controller for a subset of namespaces\n\nIf you want to use one controller for more than one namespace, but not all namespaces, you can provide additional namespaces using the command line flag `--additional-namespaces=<namespace1>,<namespace2>,<...>`. Make sure you provide appropriate roles and rolebindings in the target namespaces, so the controller can manage the secrets in there.\n\n### Can I configure the Controller unseal retries?\n\nThe answer is yes, you can configure the number of retries in your controller using the flag `--max-unseal-retries`. This flag allows you to configure the number of maximum retries to unseal your Sealed Secrets.\n\n### How to manage SealedSecrets across the cluster or specific namespaces?\n\nBy default, the controller watches for `SealedSecret` resources across **all namespaces** using the `--all-namespaces` flag (which defaults to `true`).\n\nIf you need to restrict the controller's scope, you have two options:\n- **Watch a subset of namespaces:** Use the `--additional-namespaces=<ns1>,<ns2>` flag to provide a comma-separated list of namespaces for the controller to manage.\n- **Watch only the local namespace:** Set `--all-namespaces=false` (or the environment variable `SEALED_SECRETS_ALL_NAMESPACES=false`). This is useful for multi-tenant clusters where you want isolated controllers with independent sealing keys in each namespace.\n\n## Community\n\n- [#sealed-secrets on Kubernetes Slack](https://kubernetes.slack.com/messages/sealed-secrets)\n\nClick [here](http://slack.k8s.io) to sign up to the Kubernetes Slack org.\n\n### Related projects\n\n- `kseal` A Kubeseal Companion: [https://github.com/eznix86/kseal](https://github.com/eznix86/kseal)\n- `kubeseal-convert`: [https://github.com/EladLeev/kubeseal-convert](https://github.com/EladLeev/kubeseal-convert)\n- Visual Studio Code extension: [https://marketplace.visualstudio.com/items?itemName=codecontemplator.kubeseal](https://marketplace.visualstudio.com/items?itemName=codecontemplator.kubeseal)\n- WebSeal: generates secrets in the browser: [https://socialgouv.github.io/webseal](https://socialgouv.github.io/webseal)\n- HybridEncrypt TypeScript implementation: [https://github.com/SocialGouv/aes-gcm-rsa-oaep](https://github.com/SocialGouv/aes-gcm-rsa-oaep)\n- [DEPRACATED] Sealed Secrets Operator: [https://github.com/disposab1e/sealed-secrets-operator-helm](https://github.com/disposab1e/sealed-secrets-operator-helm)\n"
  },
  {
    "path": "RELEASE-NOTES.md",
    "content": "# Release Notes\n\nLatest release:\n\n[![](https://img.shields.io/github/release/bitnami-labs/sealed-secrets.svg)](https://github.com/bitnami-labs/sealed-secrets/releases/latest)\n\n## v0.36.1\n\n- Doc/issue 501 all namespaces ([#1900](https://github.com/bitnami-labs/sealed-secrets/pull/1900))\n- Bump go 1.26.1 ([#1914](https://github.com/bitnami-labs/sealed-secrets/pull/1914))\n- Update actions/setup-go to v6.2.0 ([#1906](https://github.com/bitnami-labs/sealed-secrets/pull/1906))\n- fix: explicitly specify TCP protocol for helm SSA compatibility (#692) ([#1901](https://github.com/bitnami-labs/sealed-secrets/pull/1901))\n- docs: document GKE Warden and RBAC restrictions ([#1892](https://github.com/bitnami-labs/sealed-secrets/pull/1892))\n- Bump k8s.io/klog/v2 from 2.130.1 to 2.140.0 ([#1913](https://github.com/bitnami-labs/sealed-secrets/pull/1913))\n- chore: remove note about deprecation of helm chart. ([#1902](https://github.com/bitnami-labs/sealed-secrets/pull/1902))\n- Bump k8s.io/code-generator from 0.35.1 to 0.35.2 ([#1909](https://github.com/bitnami-labs/sealed-secrets/pull/1909))\n- Bump k8s.io/client-go from 0.35.1 to 0.35.2 ([#1908](https://github.com/bitnami-labs/sealed-secrets/pull/1908))\n- Bump distroless/static from `d90359c` to `28efbe9` in /docker ([#1912](https://github.com/bitnami-labs/sealed-secrets/pull/1912))\n- Fix oci push action ([#1899](https://github.com/bitnami-labs/sealed-secrets/pull/1899))\n\n## v0.36.0\n\n- [Security] Preserve scope during Sealed Secret rotation ([#1886](https://github.com/bitnami-labs/sealed-secrets/pull/1886))\n- [Security] Throw an error in case of inconsistencies in the Sealed Secrets ([#1885](https://github.com/bitnami-labs/sealed-secrets/pull/1885))\n- Bump distroless/static from `972618c` to `d90359c` in /docker ([#1884](https://github.com/bitnami-labs/sealed-secrets/pull/1884))\n- Set up OCI GH to release helm chart ([#1883](https://github.com/bitnami-labs/sealed-secrets/pull/1883))\n\n## v0.35.0\n\n- my namespace as key namespace ([#1867](https://github.com/bitnami-labs/sealed-secrets/pull/1867))\n- Bump go 1.25.7 ([#1880](https://github.com/bitnami-labs/sealed-secrets/pull/1880))\n- Update client-go and api 0.35.0 ([#1868](https://github.com/bitnami-labs/sealed-secrets/pull/1868))\n- Bump golang.org/x/crypto from 0.46.0 to 0.47.0 ([#1863](https://github.com/bitnami-labs/sealed-secrets/pull/1863))\n- Bump github.com/onsi/gomega from 1.38.3 to 1.39.0 ([#1865](https://github.com/bitnami-labs/sealed-secrets/pull/1865))\n- Bump github.com/onsi/ginkgo/v2 from 2.27.3 to 2.27.5 ([#1864](https://github.com/bitnami-labs/sealed-secrets/pull/1864))\n- Bump distroless/static from `4b2a093` to `cd64bec` in /docker ([#1866](https://github.com/bitnami-labs/sealed-secrets/pull/1866))\n- Bump k8s.io/code-generator from 0.34.3 to 0.35.0 ([#1858](https://github.com/bitnami-labs/sealed-secrets/pull/1858))\n\n## v0.34.0\n\n- Add kseal to README ([#1852)](https://github.com/bitnami-labs/sealed-secrets/pull/1852))\n- Bump golang version to the latest available 1.24 ([#1854](https://github.com/bitnami-labs/sealed-secrets/pull/1854))\n- Bump k8s.io/code-generator from 0.34.2 to 0.34.3 ([#1850](https://github.com/bitnami-labs/sealed-secrets/pull/1850))\n- Bump k8s.io/client-go from 0.34.2 to 0.34.3 ([#1848](https://github.com/bitnami-labs/sealed-secrets/pull/1848))\n- Bump github.com/onsi/ginkgo/v2 from 2.27.2 to 2.27.3 ([#1843](https://github.com/bitnami-labs/sealed-secrets/pull/1843))\n- Bump distroless/static from `87bce11` to `4b2a093` in /docker ([#1846](https://github.com/bitnami-labs/sealed-secrets/pull/1846))\n- Bump github.com/onsi/gomega from 1.38.2 to 1.38.3 ([#1844](https://github.com/bitnami-labs/sealed-secrets/pull/1844))\n- Bump golang.org/x/crypto from 0.45.0 to 0.46.0 ([#1845](https://github.com/bitnami-labs/sealed-secrets/pull/1845))\n- Make controllers kubeclient QPS & Burst configurable. ([#1834](https://github.com/bitnami-labs/sealed-secrets/pull/1834))\n- use default method to watch for key secrets ([#1831](https://github.com/bitnami-labs/sealed-secrets/pull/1831))\n- Bump golang.org/x/crypto from 0.44.0 to 0.45.0 in the go_modules group across 1 directory ([#1840](https://github.com/bitnami-labs/sealed-secrets/pull/1840))\n- Bump k8s.io/code-generator from 0.34.1 to 0.34.2 ([#1839](https://github.com/bitnami-labs/sealed-secrets/pull/1839))\n- Bump golang.org/x/crypto from 0.43.0 to 0.44.0 ([#1835](https://github.com/bitnami-labs/sealed-secrets/pull/1835))\n- Bump k8s.io/client-go from 0.34.1 to 0.34.2 ([#1837](https://github.com/bitnami-labs/sealed-secrets/pull/1837))\n\n## v0.33.1\n\n- Release done to fix missing helm chart code.\n\n## v0.33.0\n\n- Bump Go to 1.25.4 ([#1823](https://github.com/bitnami-labs/sealed-secrets/pull/1823))\n- Bump github.com/onsi/ginkgo/v2 from 2.26.0 to 2.27.2 ([#1820](https://github.com/bitnami-labs/sealed-secrets/pull/1820))\n- Bump golang.org/x/crypto from 0.42.0 to 0.43.0 ([#1818](https://github.com/bitnami-labs/sealed-secrets/pull/1818))\n- Bump github.com/onsi/ginkgo/v2 from 2.25.3 to 2.26.0 ([#1817](https://github.com/bitnami-labs/sealed-secrets/pull/1817))\n\n## v0.32.2\n\n- Fix controller yaml ([#1811](https://github.com/bitnami-labs/sealed-secrets/pull/1811))\n- Bump k8s.io/code-generator from 0.33.4 to 0.34.1 ([#1809](https://github.com/bitnami-labs/sealed-secrets/pull/1809))\n\n## v0.32.1\n\n- Bump distroless version ([#1804](https://github.com/bitnami-labs/sealed-secrets/pull/1804))\n\n## v0.32.0\n\n- Fix regression mismatching namespace ([#1798](https://github.com/bitnami-labs/sealed-secrets/pull/1798))\n- Bump k8s.io/apimachinery from 0.33.4 to 0.34.0 ([#1795](https://github.com/bitnami-labs/sealed-secrets/pull/1795))\n- Bump github.com/spf13/pflag from 1.0.7 to 1.0.10 ([#1794](https://github.com/bitnami-labs/sealed-secrets/pull/1794))\n- Bump github.com/onsi/ginkgo/v2 from 2.25.1 to 2.25.3 ([#1793](https://github.com/bitnami-labs/sealed-secrets/pull/1793))\n- Bump golang.org/x/crypto from 0.41.0 to 0.42.0 ([#1797](https://github.com/bitnami-labs/sealed-secrets/pull/1797))\n- Bump github.com/prometheus/client_golang from 1.23.0 to 1.23.2 ([#1796](https://github.com/bitnami-labs/sealed-secrets/pull/1796))\n- Bump github.com/onsi/gomega from 1.38.0 to 1.38.1 ([#1787](https://github.com/bitnami-labs/sealed-secrets/pull/1787))\n- Bump k8s.io/client-go from 0.33.3 to 0.33.4 ([#1774](https://github.com/bitnami-labs/sealed-secrets/pull/1774))\n- Bump k8s.io/api from 0.33.3 to 0.33.4 ([#1775](https://github.com/bitnami-labs/sealed-secrets/pull/1775))\n- Bump github.com/onsi/ginkgo/v2 from 2.23.4 to 2.24.0 ([#1776](https://github.com/bitnami-labs/sealed-secrets/pull/1776))\n- Bump k8s.io/apimachinery from 0.33.3 to 0.33.4 ([#1777](https://github.com/bitnami-labs/sealed-secrets/pull/1788))\n- Bump k8s.io/code-generator from 0.33.3 to 0.33.4 ([#1778](https://github.com/bitnami-labs/sealed-secrets/pull/1778))\n\n## v0.31.0\n\n- Helm: add watch for secrets ([#1758](https://github.com/bitnami-labs/sealed-secrets/pull/1758))\n- Simplify VIB helm chart validation ([#1771](https://github.com/bitnami-labs/sealed-secrets/pull/1771))\n- Fix: metrics cleanup for deleted SealedSecrets ([#1764](https://github.com/bitnami-labs/sealed-secrets/pull/1764))\n- Fix keyrenewperiod template chart ([#1756](https://github.com/bitnami-labs/sealed-secrets/pull/1756))\n- Fix namespace validation to prevent mismatch errors ([#1754](https://github.com/bitnami-labs/sealed-secrets/pull/1754))\n- Bump VIB action version and updates the service URL ([#1770](https://github.com/bitnami-labs/sealed-secrets/pull/1770))\n- Bump golang version to latest available one for 1.24 ([#1769](https://github.com/bitnami-labs/sealed-secrets/pull/1769))\n- Bump golang.org/x/crypto from 0.40.0 to 0.41.0 ([#1768](https://github.com/bitnami-labs/sealed-secrets/pull/1768))\n- Bump github.com/prometheus/client_golang from 1.22.0 to 1.23.0 ([#1767](https://github.com/bitnami-labs/sealed-secrets/pull/1767))\n- Bump k8s.io/api from 0.33.2 to 0.33.3 ([#1766](https://github.com/bitnami-labs/sealed-secrets/pull/1766))\n- Bump github.com/spf13/pflag from 1.0.6 to 1.0.7 ([#1765](https://github.com/bitnami-labs/sealed-secrets/pull/1765))\n- Bump k8s.io/client-go from 0.33.2 to 0.33.3 ([#1761](https://github.com/bitnami-labs/sealed-secrets/pull/1761))\n- Bump github.com/onsi/gomega from 1.37.0 to 1.38.0 ([#1760](https://github.com/bitnami-labs/sealed-secrets/pull/1760))\n- Bump k8s.io/code-generator from 0.33.2 to 0.33.3 ([#1759](https://github.com/bitnami-labs/sealed-secrets/pull/1759))\n- Bump golang.org/x/crypto from 0.39.0 to 0.40.0 ([#1755](https://github.com/bitnami-labs/sealed-secrets/pull/1755))\n- Bump k8s.io/code-generator from 0.33.1 to 0.33.2 ([#1752](https://github.com/bitnami-labs/sealed-secrets/pull/1752))\n\n## v0.30.0\n\n- Bump golang to 1.24.4 ([#1743](https://github.com/bitnami-labs/sealed-secrets/pull/1743))\n- Fix typo in RBAC namespaced roles documentation ([#1720](https://github.com/bitnami-labs/sealed-secrets/pull/1720))\n- Bump to go1.24.1 ([#1713](https://github.com/bitnami-labs/sealed-secrets/pull/1713))\n- Fix potential controller sensitive data exposure by sprig template functions ([#1703](https://github.com/bitnami-labs/sealed-secrets/pull/1703))\n- Bump golang.org/x/crypto from 0.38.0 to 0.39.0 ([#1742](https://github.com/bitnami-labs/sealed-secrets/pull/1742))\n- Bump k8s.io/client-go from 0.33.0 to 0.33.1 ([#1734](https://github.com/bitnami-labs/sealed-secrets/pull/1734))\n- Bump k8s.io/api from 0.33.0 to 0.33.1 ([#1733](https://github.com/bitnami-labs/sealed-secrets/pull/1733))\n- Bump k8s.io/code-generator from 0.33.0 to 0.33.1 ([#1732](https://github.com/bitnami-labs/sealed-secrets/pull/1732))\n- Bump golang.org/x/crypto from 0.37.0 to 0.38.0 ([#1731](https://github.com/bitnami-labs/sealed-secrets/pull/1731))\n- Bump k8s.io/client-go from 0.32.3 to 0.33.0 ([#1729](https://github.com/bitnami-labs/sealed-secrets/pull/1729))\n- Bump k8s.io/code-generator from 0.32.3 to 0.33.0 ([#1728](https://github.com/bitnami-labs/sealed-secrets/pull/1728))\n- Bump k8s.io/api from 0.32.3 to 0.33.0 ([#1730](https://github.com/bitnami-labs/sealed-secrets/pull/1730))\n- Bump golang.org/x/net from 0.37.0 to 0.38.0 in the go_modules group ([#1725](https://github.com/bitnami-labs/sealed-secrets/pull/1725))\n- Bump github.com/prometheus/client_golang from 1.21.1 to 1.22.0 ([#1724](https://github.com/bitnami-labs/sealed-secrets/pull/1724))\n- Bump github.com/onsi/gomega from 1.36.3 to 1.37.0 ([#1722](https://github.com/bitnami-labs/sealed-secrets/pull/1722))\n- Bump github.com/onsi/ginkgo/v2 from 2.23.3 to 2.23.4 ([#1723](https://github.com/bitnami-labs/sealed-secrets/pull/1723))\n- Bump golang.org/x/crypto from 0.36.0 to 0.37.0 ([#1721](https://github.com/bitnami-labs/sealed-secrets/pull/1721))\n\n## v0.29.0\n\n- Fix register a key using secret creationTimestamp instead of certificate validity timestamp ([#1681](https://github.com/bitnami-labs/sealed-secrets/pull/1681))\n- Bump to go1.23.7 ([#1714](https://github.com/bitnami-labs/sealed-secrets/pull/1714))\n- Update environment k8s version on CI ([#1688](https://github.com/bitnami-labs/sealed-secrets/pull/1688))\n- Update go tooling to 1.23.6 ([#1686](https://github.com/bitnami-labs/sealed-secrets/pull/1686))\n- Bump github.com/onsi/gomega from 1.36.2 to 1.36.3 ([#1712](https://github.com/bitnami-labs/sealed-secrets/pull/1712))\n- Bump github.com/onsi/ginkgo/v2 from 2.23.0 to 2.23.3 ([#1711](https://github.com/bitnami-labs/sealed-secrets/pull/1711))\n- Bump k8s.io/code-generator from 0.32.2 to 0.32.3 ([#1708](https://github.com/bitnami-labs/sealed-secrets/pull/1708))\n- Bump k8s.io/client-go from 0.32.2 to 0.32.3 ([#1705](https://github.com/bitnami-labs/sealed-secrets/pull/1705))\n- Bump golang.org/x/net from 0.35.0 to 0.36.0 in the go_modules group ([#1702](https://github.com/bitnami-labs/sealed-secrets/pull/1702))\n- Bump golang.org/x/crypto from 0.35.0 to 0.36.0 ([#1699](https://github.com/bitnami-labs/sealed-secrets/pull/1699))\n- Bump github.com/prometheus/client_golang from 1.21.0 to 1.21.1 ([#1699](https://github.com/bitnami-labs/sealed-secrets/pull/1699))\n- Bump github.com/onsi/ginkgo/v2 from 2.22.2 to 2.23.0 ([#1701](https://github.com/bitnami-labs/sealed-secrets/pull/1701))\n- Bump github.com/prometheus/client_golang from 1.20.5 to 1.21.0 ([#1695](https://github.com/bitnami-labs/sealed-secrets/pull/1695))\n- Bump github.com/google/go-cmp from 0.6.0 to 0.7.0 ([#1696](https://github.com/bitnami-labs/sealed-secrets/pull/1696))\n- Bump golang.org/x/crypto from 0.33.0 to 0.35.0 ([#1697](https://github.com/bitnami-labs/sealed-secrets/pull/1697))\n- Bump k8s.io/client-go from 0.32.1 to 0.32.2 ([#1691](https://github.com/bitnami-labs/sealed-secrets/pull/1691))\n- Bump k8s.io/code-generator from 0.32.1 to 0.32.2 ([#1693](https://github.com/bitnami-labs/sealed-secrets/pull/1693))\n- Bump golang.org/x/crypto from 0.32.0 to 0.33.0 ([#1685](https://github.com/bitnami-labs/sealed-secrets/pull/1685))\n- Bump github.com/spf13/pflag from 1.0.5 to 1.0.6 ([#1683](https://github.com/bitnami-labs/sealed-secrets/pull/1683))\n- Bump k8s.io/client-go from 0.32.0 to 0.32.1 ([#1678](https://github.com/bitnami-labs/sealed-secrets/pull/1678))\n- Bump k8s.io/code-generator from 0.32.0 to 0.32.1 ([#1677](https://github.com/bitnami-labs/sealed-secrets/pull/1677))\n\n## v0.28.0\n\n- fix: explicitly set resourceFieldRef.divisor ([#1655](https://github.com/bitnami-labs/sealed-secrets/pull/1655))\n- Fix deprecated functions for bumping client-go ([#1667](https://github.com/bitnami-labs/sealed-secrets/pull/1667))\n- Bump github.com/onsi/ginkgo/v2 from 2.22.1 to 2.22.2 ([#1670](https://github.com/bitnami-labs/sealed-secrets/pull/1670))\n- Bump golang.org/x/crypto from 0.31.0 to 0.32.0 ([#1671](https://github.com/bitnami-labs/sealed-secrets/pull/1671))\n- Bump github.com/onsi/gomega from 1.36.1 to 1.36.2 ([#1669](https://github.com/bitnami-labs/sealed-secrets/pull/1669))\n- Bump github.com/onsi/ginkgo/v2 from 2.22.0 to 2.22.1 ([#1668](https://github.com/bitnami-labs/sealed-secrets/pull/1668))\n- Bump github.com/onsi/gomega from 1.36.0 to 1.36.1 ([#1664](https://github.com/bitnami-labs/sealed-secrets/pull/1664))\n- Bump golang.org/x/crypto from 0.30.0 to 0.31.0 ([#1659](https://github.com/bitnami-labs/sealed-secrets/pull/1659))\n- Bump golang.org/x/crypto from 0.29.0 to 0.30.0 ([#1657](https://github.com/bitnami-labs/sealed-secrets/pull/1657))\n\n## v0.27.3\n\n- Bump k8s.io/apimachinery from 0.31.2 to 0.31.3 ([#1642](https://github.com/bitnami-labs/sealed-secrets/pull/1642))\n- Bump k8s.io/code-generator from 0.31.2 to 0.31.3 ([#1643](https://github.com/bitnami-labs/sealed-secrets/pull/1643))\n- Bump github.com/onsi/gomega from 1.35.1 to 1.36.0 ([#1645](https://github.com/bitnami-labs/sealed-secrets/pull/1645))\n- re-introduce install instructions with to releases ([#1649](https://github.com/bitnami-labs/sealed-secrets/pull/1649))\n- Properly error out when input file doesn't exist ([#1640](https://github.com/bitnami-labs/sealed-secrets/pull/1640))\n- Bump github.com/onsi/ginkgo/v2 from 2.21.0 to 2.22.0 ([#1641](https://github.com/bitnami-labs/sealed-secrets/pull/1641))\n- Bump golang.org/x/crypto from 0.28.0 to 0.29.0 ([#1635](https://github.com/bitnami-labs/sealed-secrets/pull/1635))\n- Configure max retries ([#1633](https://github.com/bitnami-labs/sealed-secrets/pull/1633))\n- Label \"app.kubernetes.io/instance\" in the Prometheus metric ([#1620](https://github.com/bitnami-labs/sealed-secrets/pull/1620))\n- Bump github.com/onsi/gomega from 1.34.2 to 1.35.1 ([#1624](https://github.com/bitnami-labs/sealed-secrets/pull/1624))\n- Adding keyttl and keycutofftime options to helm chart ([#1610](https://github.com/bitnami-labs/sealed-secrets/pull/1610))\n- Bump github.com/onsi/ginkgo/v2 from 2.20.2 to 2.21.0 ([#1623](https://github.com/bitnami-labs/sealed-secrets/pull/1623))\n\n## v0.27.2\n\n- feature: Show error if there's no secret to encode ([#1580](https://github.com/bitnami-labs/sealed-secrets/pull/1580))\n- feature: allow container port configuration ([#1606](https://github.com/bitnami-labs/sealed-secrets/pull/1606))\n- chore: Update go version to 1.22.8 ([#1621](https://github.com/bitnami-labs/sealed-secrets/pull/1621))\n- chore: Update the TCSP settings for helm testing ([#1608](https://github.com/bitnami-labs/sealed-secrets/pull/1608))\n- chore: Redirect external site to the GitHub Repository ([#1589](https://github.com/bitnami-labs/sealed-secrets/pull/1589))\n- chore: Update dependencies (Several automatic PRs)\n\n## v0.27.1\n\n- chore: Update dependencies ([#1565](https://github.com/bitnami-labs/sealed-secrets/pull/1565))\n- chore: Bump golang.org/x/crypto from 0.24.0 to 0.25.0 ([#1561](https://github.com/bitnami-labs/sealed-secrets/pull/1561))\n- chore: Bump k8s.io/klog/v2 from 2.130.0 to 2.130.1 ([#1558](https://github.com/bitnami-labs/sealed-secrets/pull/1558))\n- chore: Improve release process ([#1559](https://github.com/bitnami-labs/sealed-secrets/pull/1559))\n\n## v0.27.0\n\n- feature: loadbalancerclass ([#1545](https://github.com/bitnami-labs/sealed-secrets/pull/1545))\n- Add sprig function library for templating ([#1542](https://github.com/bitnami-labs/sealed-secrets/pull/1542))\n- Update install instructions for consistent HTTP request package ([#1546](https://github.com/bitnami-labs/sealed-secrets/pull/1546))\n- Bump k8s.io/client-go from 0.30.1 to 0.30.2 ([#1552](https://github.com/bitnami-labs/sealed-secrets/pull/1552))\n- Bump k8s.io/klog/v2 from 2.120.1 to 2.130.0 ([#1551](https://github.com/bitnami-labs/sealed-secrets/pull/1551))\n- Bump k8s.io/code-generator from 0.30.1 to 0.30.2 ([#1550](https://github.com/bitnami-labs/sealed-secrets/pull/1550))\n- Bump golang.org/x/crypto from 0.23.0 to 0.24.0 ([#1544](https://github.com/bitnami-labs/sealed-secrets/pull/1544))\n- Bump github.com/onsi/ginkgo/v2 from 2.17.3 to 2.19.0 ([#1540](https://github.com/bitnami-labs/sealed-secrets/pull/1540))\n\n## v0.26.3\n\n### Changelog\n\n- fix: code generation ([#1536](https://github.com/bitnami-labs/sealed-secrets/pull/1536))\n- fix: show field name in error message when base64 decoding fails ([#1519](https://github.com/bitnami-labs/sealed-secrets/pull/1519))\n- helm: Set `GOMAXPROCS` and `GOMEMLIMIT` environment variables ([#1528](https://github.com/bitnami-labs/sealed-secrets/pull/1528))\n- docs: mention limitation of backup with key renewal ([#1533](https://github.com/bitnami-labs/sealed-secrets/pull/1533))\n- chore: update dependencies ([#1535](https://github.com/bitnami-labs/sealed-secrets/pull/1535))\n- chore: Bump k8s.io/code-generator from 0.30.0 to 0.30.1 ([#1529](https://github.com/bitnami-labs/sealed-secrets/pull/1529))\n- chore: Bump k8s.io/client-go from 0.30.0 to 0.30.1 ([#1532](https://github.com/bitnami-labs/sealed-secrets/pull/1532))\n- chore: Bump github.com/onsi/ginkgo/v2 from 2.17.2 to 2.17.3 ([#1527](https://github.com/bitnami-labs/sealed-secrets/pull/1527))\n- chore: Bump github.com/prometheus/client_golang from 1.19.0 to 1.19.1 ([#1526](https://github.com/bitnami-labs/sealed-secrets/pull/1526))\n- chore: Bump k8s.io/code-generator from 0.29.3 to 0.30.0 ([#1513](https://github.com/bitnami-labs/sealed-secrets/pull/1513))\n- chore: Update dependencies ([#1524](https://github.com/bitnami-labs/sealed-secrets/pull/1524))\n- chore: Bump github.com/onsi/gomega from 1.33.0 to 1.33.1 ([#1522](https://github.com/bitnami-labs/sealed-secrets/pull/1522))\n- chore: Bump github.com/onsi/ginkgo/v2 from 2.17.1 to 2.17.2 ([#1520](https://github.com/bitnami-labs/sealed-secrets/pull/1520))\n- chore: Bump github.com/onsi/gomega from 1.32.0 to 1.33.0 ([#1512](https://github.com/bitnami-labs/sealed-secrets/pull/1512))\n- chore: increase vib timeout ([#1509](https://github.com/bitnami-labs/sealed-secrets/pull/1509))\n- chore: fix publish-release workflow ([#1508](https://github.com/bitnami-labs/sealed-secrets/pull/1508))\n- chore: Bump golang.org/x/crypto from 0.21.0 to 0.22.0 ([#1505](https://github.com/bitnami-labs/sealed-secrets/pull/1505))\n\n## v0.26.2\n\n### Changelog\n\n- fix: update dependencies and version for CVE-2023-45288 ([#1501](https://github.com/bitnami-labs/sealed-secrets/pull/1501))\n- fix(helm): role binding annotations ([#1494](https://github.com/bitnami-labs/sealed-secrets/pull/1494))\n- chore: update cosign version ([#1495](https://github.com/bitnami-labs/sealed-secrets/pull/1495))\n- chore: Bump github.com/onsi/ginkgo/v2 from 2.16.0 to 2.17.1 ([#1497](https://github.com/bitnami-labs/sealed-secrets/pull/1497))\n- chore: Bump k8s.io/client-go from 0.29.2 to 0.29.3 ([#1486](https://github.com/bitnami-labs/sealed-secrets/pull/1486))\n- chore: Bump k8s.io/code-generator from 0.29.2 to 0.29.3 ([#1488](https://github.com/bitnami-labs/sealed-secrets/pull/1488))\n- chore: Bump github.com/onsi/gomega from 1.31.1 to 1.32.0 ([#1489](https://github.com/bitnami-labs/sealed-secrets/pull/1489))\n- chore: Bump k8s.io/apimachinery from 0.29.2 to 0.29.3 ([#1490](https://github.com/bitnami-labs/sealed-secrets/pull/1490))\n- chore: Update security contact and other references DL to the new team one ([#1500](https://github.com/bitnami-labs/sealed-secrets/pull/1500))\n\n## v0.26.1\n\n### Changelog\n\n- fix: panic when patching empty secret ([#1474](https://github.com/bitnami-labs/sealed-secrets/pull/1474))\n- fix: Modify LastUpdateTime when the Sealed Secrets is being updated ([#1475](https://github.com/bitnami-labs/sealed-secrets/pull/1475))\n- fix: Bring back private keys logging ([#1481](https://github.com/bitnami-labs/sealed-secrets/pull/1481))\n- fix: missing common annotations in the helm chart ([#1471](https://github.com/bitnami-labs/sealed-secrets/pull/1471))\n- fix: Add metrics port to allow ingress traffic in the netpols ([#1473](https://github.com/bitnami-labs/sealed-secrets/pull/1473))\n- chore: Bump google.golang.org/protobuf from 1.32.0 to 1.33.0 ([#1480](https://github.com/bitnami-labs/sealed-secrets/pull/1480))\n- chore: Bump golang.org/x/crypto from 0.20.0 to 0.21.0 ([#1477](https://github.com/bitnami-labs/sealed-secrets/pull/1477))\n- chore: Bump github.com/onsi/ginkgo/v2 from 2.15.0 to 2.16.0 ([#1478](https://github.com/bitnami-labs/sealed-secrets/pull/1478))\n- chore: Bump github.com/prometheus/client_golang from 1.18.0 to 1.19.0 ([#1476](https://github.com/bitnami-labs/sealed-secrets/pull/1476))\n- chore: Bump golang.org/x/crypto from 0.19.0 to 0.20.0 ([#1472](https://github.com/bitnami-labs/sealed-secrets/pull/1472))\n- chore: Bump k8s.io/code-generator from 0.29.1 to 0.29.2 ([#1467](https://github.com/bitnami-labs/sealed-secrets/pull/1467))\n\n## v0.26.0\n\n### Changelog\n\n- feat: Implement structured logging ([#1438](https://github.com/bitnami-labs/sealed-secrets/pull/1438))\n- feat: [helm] add rbac.proxier config ([#1451](https://github.com/bitnami-labs/sealed-secrets/pull/1451))\n- docs: Add clarity around template Secret fields ([#1456](https://github.com/bitnami-labs/sealed-secrets/pull/1456))\n- docs: [helm] adding disable keyrenewperiod comment ([#1455](https://github.com/bitnami-labs/sealed-secrets/pull/1455))\n- chore: Update Go version and dependencies ([#1460](https://github.com/bitnami-labs/sealed-secrets/pull/1460))\n- chore: Bump golang.org/x/crypto from 0.18.0 to 0.19.0 ([#1458](https://github.com/bitnami-labs/sealed-secrets/pull/1458))\n- chore: Bump k8s.io/client-go from 0.29.0 to 0.29.1 ([#1452](https://github.com/bitnami-labs/sealed-secrets/pull/1452))\n- chore: Bump k8s.io/code-generator from 0.29.0 to 0.29.1 ([#1441](https://github.com/bitnami-labs/sealed-secrets/pull/1441))\n- chore: Bump k8s.io/api from 0.29.0 to 0.29.1 ([#1443](https://github.com/bitnami-labs/sealed-secrets/pull/1443))\n- chore: Bump k8s.io/klog/v2 from 2.120.0 to 2.120.1 ([#1439](https://github.com/bitnami-labs/sealed-secrets/pull/1439))\n- chore: Bump github.com/onsi/gomega from 1.30.0 to 1.31.1 ([#1440](https://github.com/bitnami-labs/sealed-secrets/pull/1440))\n\n## v0.25.0\n\n### Changelog\n\n- feat: support immutable secrets ([#1395](https://github.com/bitnami-labs/sealed-secrets/pull/1395))\n- Update dependencies ([#1411](https://github.com/bitnami-labs/sealed-secrets/pull/1411))\n- Support fetching certificate URL via proxy environment variables ([#1419](https://github.com/bitnami-labs/sealed-secrets/pull/1419))\n- Bump github.com/onsi/ginkgo/v2 from 2.13.2 to 2.14.0 ([#1432](https://github.com/bitnami-labs/sealed-secrets/pull/1432)\n- Bump k8s.io/klog/v2 from 2.110.1 to 2.120.0 ([#1431](https://github.com/bitnami-labs/sealed-secrets/pull/1431))\n- Bump golang.org/x/crypto from 0.17.0 to 0.18.0 ([#1425](https://github.com/bitnami-labs/sealed-secrets/pull/1425))\n- Bump github.com/prometheus/client_golang from 1.17.0 to 1.18.0 ([#1421](https://github.com/bitnami-labs/sealed-secrets/pull/1421))\n- Bump k8s.io/code-generator from 0.28.4 to 0.29.0 ([#1406](https://github.com/bitnami-labs/sealed-secrets/pull/1406))\n- Bump golang.org/x/crypto from 0.16.0 to 0.17.0 ([#1405](https://github.com/bitnami-labs/sealed-secrets/pull/1405))\n\n## v0.24.5\n\n### Changelog\n\n- feat: Helm - Add sources ([#1383](https://github.com/bitnami-labs/sealed-secrets/pull/1383))\n- Update golang to the latest tooling version ([#1398](https://github.com/bitnami-labs/sealed-secrets/pull/1398))\n- Bump github.com/onsi/ginkgo/v2 from 2.13.1 to 2.13.2 ([#1397](https://github.com/bitnami-labs/sealed-secrets/pull/1397))\n- Bump golang.org/x/crypto from 0.15.0 to 0.16.0 ([#1394](https://github.com/bitnami-labs/sealed-secrets/pull/1394))\n- Bump k8s.io/code-generator from 0.28.3 to 0.28.4  ([#1390](https://github.com/bitnami-labs/sealed-secrets/pull/1390))\n- Bump k8s.io/client-go from 0.28.3 to 0.28.4  ([#1389](https://github.com/bitnami-labs/sealed-secrets/pull/1389))\n- Bump k8s.io/client-go from 0.28.3 to 0.28.4  ([#1389](https://github.com/bitnami-labs/sealed-secrets/pull/1389))\n\n## v0.24.4\n\n### Changelog\n\n- kubeseal: write help message to stdout ([#1377](https://github.com/bitnami-labs/sealed-secrets/pull/1377))\n- fix: Set up LastTransitionTime in case that it is empty ([#1370](https://github.com/bitnami-labs/sealed-secrets/pull/1370))\n- Bump github.com/onsi/gomega from 1.29.0 to 1.30.0 ([#1376](https://github.com/bitnami-labs/sealed-secrets/pull/1376))\n- Bump golang.org/x/crypto from 0.14.0 to 0.15.0 ([#1375](https://github.com/bitnami-labs/sealed-secrets/pull/1375))\n- Bump github.com/onsi/ginkgo/v2 from 2.13.0 to 2.13.1 ([#1374](https://github.com/bitnami-labs/sealed-secrets/pull/1374))\n- Bump k8s.io/klog/v2 from 2.100.1 to 2.110.1 ([#1367](https://github.com/bitnami-labs/sealed-secrets/pull/1367))\n\n## v0.24.3\n\n### Changelog\n\n- fix a bug that kept a sealed secret's generation and observedgeneration out of sync ([#1360](https://github.com/bitnami-labs/sealed-secrets/pull/1360))\n- fix: add pdb ([#1340](https://github.com/bitnami-labs/sealed-secrets/pull/1340))\n- Bump k8s.io/code-generator from 0.28.2 to 0.28.3 ([#1358](https://github.com/bitnami-labs/sealed-secrets/pull/1340))\n- Bump github.com/onsi/gomega from 1.28.1 to 1.29.0 ([#1357](https://github.com/bitnami-labs/sealed-secrets/pull/1357))\n- Bump github.com/mattn/go-isatty from 0.0.19 to 0.0.20 ([#1353](https://github.com/bitnami-labs/sealed-secrets/pull/1353))\n- Bump github.com/onsi/gomega from 1.28.0 to 1.28.1 ([#1351](https://github.com/bitnami-labs/sealed-secrets/pull/1351))\n- Bump k8s.io/client-go from 0.28.2 to 0.28.3 ([#1350](https://github.com/bitnami-labs/sealed-secrets/pull/1350))\n- Bump k8s.io/api from 0.28.2 to 0.28.3 ([#1349](https://github.com/bitnami-labs/sealed-secrets/pull/1349))\n- Bump github.com/google/go-cmp from 0.5.9 to 0.6.0 ([#1348](https://github.com/bitnami-labs/sealed-secrets/pull/1348))\n\n## v0.24.2\n\n### Changelog\n\n- Fix issue where sealed secrets status is not updated if sealed secret…  ([#1295](https://github.com/bitnami-labs/sealed-secrets/pull/1295))\n- Bump golang.org/x/crypto from 0.13.0 to 0.14.0([#1341](https://github.com/bitnami-labs/sealed-secrets/pull/1341))\n- Bump github.com/onsi/ginkgo/v2 from 2.12.1 to 2.13.0 ([#1342](https://github.com/bitnami-labs/sealed-secrets/pull/1342))\n- Bump golang.org/x/net from 0.14.0 to 0.17.0 ([#1344](https://github.com/bitnami-labs/sealed-secrets/pull/1344))\n\n## v0.24.1\n\n### Changelog\n\n- fix: remove trailing dashes for multidoc yaml ([#1335](https://github.com/bitnami-labs/sealed-secrets/pull/1335))\n\n## v0.24.0\n\n### Changelog\n\n- feat: multidoc support for yaml and json ([#1304](https://github.com/bitnami-labs/sealed-secrets/pull/1304))\n- Delete repeating warning message ([#1303](https://github.com/bitnami-labs/sealed-secrets/pull/1303))\n- Add dashboard configmap annotations ([#1302](https://github.com/bitnami-labs/sealed-secrets/pull/1302))\n- Update the golang version to the latest available one ([#1318](https://github.com/bitnami-labs/sealed-secrets/pull/1318))\n- Update Linux installation process on README to have a way to dynamically get kubeseal version number ([#1294](https://github.com/bitnami-labs/sealed-secrets/pull/1294))\n- Bump golang.org/x/crypto from 0.12.0 to 0.13.0 ([#1319](https://github.com/bitnami-labs/sealed-secrets/pull/1319))\n- Bump github.com/onsi/ginkgo/v2 from 2.11.0 to 2.12.0 ([#1310](https://github.com/bitnami-labs/sealed-secrets/pull/1310))\n- Bump k8s.io/client-go from 0.28.0 to 0.28.1 ([#1308](https://github.com/bitnami-labs/sealed-secrets/pull/1308))\n- Bump k8s.io/code-generator from 0.28.0 to 0.28.1 ([#1307](https://github.com/bitnami-labs/sealed-secrets/pull/1307))\n- Bump k8s.io/code-generator from 0.27.4 to 0.28.0 ([#1300](https://github.com/bitnami-labs/sealed-secrets/pull/1300))\n- Bump k8s.io/client-go from 0.27.4 to 0.28.0 ([#1297](https://github.com/bitnami-labs/sealed-secrets/pull/1297))\n\n## v0.23.1\n\n### Changelog\n\n- securityContext adjusted ([#1261](https://github.com/bitnami-labs/sealed-secrets/pull/1261))\n- allow changing the default revisionHistoryLimit ([#1286](https://github.com/bitnami-labs/sealed-secrets/pull/1286))\n- Bump k8s.io/client-go from 0.27.3 to 0.27.4 ([#1277](https://github.com/bitnami-labs/sealed-secrets/pull/1277))\n- Bump k8s.io/code-generator from 0.27.3 to 0.27.4 ([#1278](https://github.com/bitnami-labs/sealed-secrets/pull/1278))\n- Bump github.com/onsi/gomega from 1.27.8 to 1.27.10 ([#1279](https://github.com/bitnami-labs/sealed-secrets/pull/1279))\n- Bump k8s.io/api from 0.27.3 to 0.27.4 ([#1281](https://github.com/bitnami-labs/sealed-secrets/pull/1281))\n- Bump golang.org/x/crypto from 0.11.0 to 0.12.0 ([#1287](https://github.com/bitnami-labs/sealed-secrets/pull/1287)\n\n## v0.23.0\n\n### Changelog\n\n- Add option for custom annotations and labels on sealing keypairs ([#1250](https://github.com/bitnami-labs/sealed-secrets/pull/1250))\n- Add option to patch secrets instead of clobbering them ([#1259](https://github.com/bitnami-labs/sealed-secrets/pull/1259))\n- Improve CLI UX error message while service is not found ([#1256](https://github.com/bitnami-labs/sealed-secrets/pull/1256))\n- Add namespaced roles support to Helm chart ([#1240](https://github.com/bitnami-labs/sealed-secrets/pull/1240))\n- add --log-info-stdout to chart ([#1238](https://github.com/bitnami-labs/sealed-secrets/pull/1238))\n- Fix networkpolicy port + add egress ([#1243](https://github.com/bitnami-labs/sealed-secrets/pull/1243))\n- Create index for Sealed Secrets public documentation ([#1264](https://github.com/bitnami-labs/sealed-secrets/pull/1264))\n- Getting started page ([#1253](https://github.com/bitnami-labs/sealed-secrets/pull/1253))\n- Create a FAQ document for Sealed Secrets public documentation ([#1269](https://github.com/bitnami-labs/sealed-secrets/pull/1269))\n- Create a cryptography document for Sealed Secrets public documentation ([#1267](https://github.com/bitnami-labs/sealed-secrets/pull/1267))\n- Validate existing Sealed Secrets document ([#1266](https://github.com/bitnami-labs/sealed-secrets/pull/1266))\n- added support policy to readme ([#1265](https://github.com/bitnami-labs/sealed-secrets/pull/1265))\n- Add missing document seperator ([#1260](https://github.com/bitnami-labs/sealed-secrets/pull/1260))\n- Enable full linter support for golangci-lint ([#1262](https://github.com/bitnami-labs/sealed-secrets/pull/1262))\n- Update minikube K8S versions ([#1251](https://github.com/bitnami-labs/sealed-secrets/pull/1251))\n- Bump github.com/onsi/ginkgo/v2 from 2.10.0 to 2.11.0 ([#1254](https://github.com/bitnami-labs/sealed-secrets/pull/1254))\n- Bump k8s.io/code-generator from 0.27.2 to 0.27.3 ([#1255](https://github.com/bitnami-labs/sealed-secrets/pull/1255))\n- Bump golang.org/x/crypto from 0.10.0 to 0.11.0 ([#1268](https://github.com/bitnami-labs/sealed-secrets/pull/1268))\n- Bump github.com/prometheus/client_golang from 1.15.1 to 1.16.0 ([#1247](https://github.com/bitnami-labs/sealed-secrets/pull/1247))\n- Bump golang.org/x/crypto from 0.9.0 to 0.10.0 ([#1248](https://github.com/bitnami-labs/sealed-secrets/pull/1248))\n- Bump k8s.io/client-go from 0.27.2 to 0.27.3 ([#1244](https://github.com/bitnami-labs/sealed-secrets/pull/1244))\n\n## v0.22.0\n\n### Changelog\n\n- Feature allow to skip set owner references ([#1200](https://github.com/bitnami-labs/sealed-secrets/pull/1200))\n- Add additionalPrinterColumns for status and age ([#1217](https://github.com/bitnami-labs/sealed-secrets/pull/1217))\n- Add replicas default value to the deployment manifest ([#1219](https://github.com/bitnami-labs/sealed-secrets/pull/1219))\n- Create SECURITY.md ([#1226](https://github.com/bitnami-labs/sealed-secrets/pull/1226))\n- Fix doc generated code directory ([#1227](https://github.com/bitnami-labs/sealed-secrets/pull/1227))\n- Update generated code ([#1228](https://github.com/bitnami-labs/sealed-secrets/pull/1228))\n- Update maintainers list ([#1237](https://github.com/bitnami-labs/sealed-secrets/pull/1237))\n- Bump github.com/onsi/ginkgo/v2 from 2.9.4 to 2.9.5 ([#1215](https://github.com/bitnami-labs/sealed-secrets/pull/1215))\n- Bump golang.org/x/crypto from 0.8.0 to 0.9.0 ([#1216](https://github.com/bitnami-labs/sealed-secrets/pull/1216))\n- Bump k8s.io/apimachinery from 0.27.1 to 0.27.2 ([#1221](https://github.com/bitnami-labs/sealed-secrets/pull/1221))\n- Bump k8s.io/client-go from 0.27.1 to 0.27.2 ([#1222](https://github.com/bitnami-labs/sealed-secrets/pull/1222))\n- Bump github.com/mattn/go-isatty from 0.0.18 to 0.0.19 ([#1223](https://github.com/bitnami-labs/sealed-secrets/pull/1223))\n- Bump k8s.io/code-generator from 0.27.1 to 0.27.2 ([#1225](https://github.com/bitnami-labs/sealed-secrets/pull/1225))\n- Bump github.com/onsi/gomega from 1.27.6 to 1.27.7 ([#1229](https://github.com/bitnami-labs/sealed-secrets/pull/1229))\n- Bump github.com/onsi/ginkgo/v2 from 2.9.5 to 2.9.7 ([#1231](https://github.com/bitnami-labs/sealed-secrets/pull/1231))\n- Bump github.com/onsi/gomega from 1.27.7 to 1.27.8 ([#1234](https://github.com/bitnami-labs/sealed-secrets/pull/1234))\n- Bump github.com/onsi/ginkgo/v2 from 2.9.7 to 2.10.0 ([#1235](https://github.com/bitnami-labs/sealed-secrets/pull/1235))\n\n## v0.21.0\n\n### Changelog\n\n- Enable logging info to stdout([#1195](https://github.com/bitnami-labs/sealed-secrets/pull/1195))\n- Bump github.com/prometheus/client_golang from 1.15.0 to 1.15.1 ([#1204](https://github.com/bitnami-labs/sealed-secrets/pull/1204))\n- Bump github.com/onsi/ginkgo/v2 from 2.9.2 to 2.9.4 ([#1203](https://github.com/bitnami-labs/sealed-secrets/pull/1203))\n- Bump k8s.io/klog/v2 from 2.90.1 to 2.100.1 ([#1201](https://github.com/bitnami-labs/sealed-secrets/pull/1201))\n- Bump k8s.io/code-generator from 0.26.3 to 0.27.1 ([#1188](https://github.com/bitnami-labs/sealed-secrets/pull/1188)) \n- Bump k8s.io/client-go from 0.26.3 to 0.27.1 ([#1187](https://github.com/bitnami-labs/sealed-secrets/pull/1187)) \n- Bump github.com/prometheus/client_golang from 1.14.0 to 1.15.0 ([#1189](https://github.com/bitnami-labs/sealed-secrets/pull/1189)) \n\n## v0.20.5\n\n### Changelog\n\n- Generate embedded ObjectMeta in CRD ([#1177](https://github.com/bitnami-labs/sealed-secrets/pull/1177))\n- Sign images using Cosign v2 ([#1176](https://github.com/bitnami-labs/sealed-secrets/pull/1176))\n- ReProcess only on spec changes ([#1174](https://github.com/bitnami-labs/sealed-secrets/pull/1174))\n- Upgrade sealed secrets to Go 1.20 ([#1173](https://github.com/bitnami-labs/sealed-secrets/pull/1173))\n- Fix cosign command for goreleaser ([#1180](https://github.com/bitnami-labs/sealed-secrets/pull/1180))\n- Fix kubeseal image sign for cosign v2 ([#1182](https://github.com/bitnami-labs/sealed-secrets/pull/1182))\n- Remove automountServiceAccountToken parameter ([#1162](https://github.com/bitnami-labs/sealed-secrets/pull/1162))\n- Verify chart with secret recreation disabled ([#1163](https://github.com/bitnami-labs/sealed-secrets/pull/1163))\n- Bump golang.org/x/crypto from 0.7.0 to 0.8.0 ([#1175](https://github.com/bitnami-labs/sealed-secrets/pull/1175))\n- Bump github.com/onsi/gomega from 1.27.5 to 1.27.6 ([#1169](https://github.com/bitnami-labs/sealed-secrets/pull/1169))\n- Bump github.com/onsi/gomega from 1.27.4 to 1.27.5 ([#1168](https://github.com/bitnami-labs/sealed-secrets/pull/1168))\n- Bump github.com/mattn/go-isatty from 0.0.17 to 0.0.18 ([#1167](https://github.com/bitnami-labs/sealed-secrets/pull/1167))\n- Bump github.com/onsi/ginkgo/v2 from 2.9.1 to 2.9.2 ([#1166](https://github.com/bitnami-labs/sealed-secrets/pull/1166))\n- Bump k8s.io/apimachinery from 0.26.2 to 0.26.3 ([#1160](https://github.com/bitnami-labs/sealed-secrets/pull/1160))\n- Bump k8s.io/code-generator from 0.26.2 to 0.26.3 ([#1159](https://github.com/bitnami-labs/sealed-secrets/pull/1159))\n- Bump k8s.io/api from 0.26.2 to 0.26.3 ([#1158](https://github.com/bitnami-labs/sealed-secrets/pull/1158))\n- Bump k8s.io/client-go from 0.26.2 to 0.26.3 ([#1157](https://github.com/bitnami-labs/sealed-secrets/pull/1157))\n- Update VIB release tag format ([#1165](https://github.com/bitnami-labs/sealed-secrets/pull/1165))\n- Update VIB action ([#1164](https://github.com/bitnami-labs/sealed-secrets/pull/1164))\n- Include dockerhub pull statistics in the project README ([#1172](https://github.com/bitnami-labs/sealed-secrets/pull/1172))\n\n## v0.20.4\n\nIncomplete release\n\n## v0.20.3\n\nIncomplete release\n\n## v0.20.2\n\n### Changelog\n\n- Fix panic when skip recreate is enabled ([#1152](https://github.com/bitnami-labs/sealed-secrets/pull/1152))\n\n## v0.20.1\n\n### Changelog\n\n- Parametrize cluster role name ([#1141](https://github.com/bitnami-labs/sealed-secrets/pull/1141))\n- Allow automountServiceAccountToken to be set to false ([#1128](https://github.com/bitnami-labs/sealed-secrets/pull/1128))\n- Allow to disable secret auto-recreation ([#1118](https://github.com/bitnami-labs/sealed-secrets/pull/1118))\n- Bump github.com/onsi/gomega from 1.27.2 to 1.27.4 ([#1143](https://github.com/bitnami-labs/sealed-secrets/pull/1143))\n- Bump k8s.io/client-go from 0.26.1 to 0.26.2 ([#1136](https://github.com/bitnami-labs/sealed-secrets/pull/1136))\n- Bump k8s.io/code-generator from 0.26.1 to 0.26.2 ([#1137](https://github.com/bitnami-labs/sealed-secrets/pull/1137))\n- Bump k8s.io/api from 0.26.1 to 0.26.2 ([#1135](https://github.com/bitnami-labs/sealed-secrets/pull/1135))\n- Bump github.com/onsi/gomega from 1.27.1 to 1.27.2 ([#1134](https://github.com/bitnami-labs/sealed-secrets/pull/1134))\n- Bump k8s.io/apimachinery from 0.26.1 to 0.26.2 ([#1133](https://github.com/bitnami-labs/sealed-secrets/pull/1133))\n- Bump k8s.io/klog/v2 from 2.90.0 to 2.90.1 ([#1132](https://github.com/bitnami-labs/sealed-secrets/pull/1132))\n- Bump github.com/onsi/ginkgo/v2 from 2.8.3 to 2.9.0 ([#1131](https://github.com/bitnami-labs/sealed-secrets/pull/1131))\n- Bump golang.org/x/crypto from 0.6.0 to 0.7.0 ([#1130](https://github.com/bitnami-labs/sealed-secrets/pull/1130))\n- Ensure vib runs only when PR is approved ([#1121](https://github.com/bitnami-labs/sealed-secrets/pull/1121))\n- Run VIB Helm chart validations on push to main ([#1140](https://github.com/bitnami-labs/sealed-secrets/pull/1140))\n- Update parameters table ([#1139](https://github.com/bitnami-labs/sealed-secrets/pull/1139))\n- Update docs ([#1127](https://github.com/bitnami-labs/sealed-secrets/pull/1127))\n\n## v0.20.0\n\nIncomplete release\n\n## v0.19.5\n\n### Changelog\n\n- Automated controller test on Openshift platforms (using ([VMware Image Builder](https://tanzu.vmware.com/image-builder)) ([#1107](https://github.com/bitnami-labs/sealed-secrets/pull/1107)).\n- We now generate a Carvel package distribution of the controller ([#1104](https://github.com/bitnami-labs/sealed-secrets/pull/1104)).\n- Bump golang.org/x/crypto from 0.5.0 to 0.6.0 ([#1108](https://github.com/bitnami-labs/sealed-secrets/pull/1108)).\n- Bump github.com/onsi/gomega from 1.25.0 to 1.26.0 ([#1103](https://github.com/bitnami-labs/sealed-secrets/pull/1103)).\n- Bump k8s.io/code-generator from 0.26.0 to 0.26.1 ([#1102](https://github.com/bitnami-labs/sealed-secrets/pull/1102)).\n- Bump github.com/onsi/ginkgo/v2 from 2.7.0 to 2.8.0 ([#1101](https://github.com/bitnami-labs/sealed-secrets/pull/1101)).\n- Bump k8s.io/api from 0.26.0 to 0.26.1 ([#1097](https://github.com/bitnami-labs/sealed-secrets/pull/1097)).\n- Bump k8s.io/client-go from 0.26.0 to 0.26.1  ([#1096](https://github.com/bitnami-labs/sealed-secrets/pull/1096)).\n- Bump k8s.io/klog/v2 from 2.80.1 to 2.90.0 ([#1094](https://github.com/bitnami-labs/sealed-secrets/pull/1094)).\n- Bump k8s.io/apimachinery from 0.26.0 to 0.26.1 ([#1093](https://github.com/bitnami-labs/sealed-secrets/pull/1093)).\n\n## v0.19.4\n\n### Changelog\n\n- Bump github.com/onsi/ginkgo/v2 from 2.6.1 to 2.7.0 ([#1086](https://github.com/bitnami-labs/sealed-secrets/pull/1086)).\n- Bump golang.org/x/crypto from 0.4.0 to 0.5.0 ([#1085](https://github.com/bitnami-labs/sealed-secrets/pull/1085)).\n- Bump github.com/mattn/go-isatty from 0.0.16 to 0.0.17 ([#1083](https://github.com/bitnami-labs/sealed-secrets/pull/1083)).\n- Bump github.com/onsi/gomega from 1.24.1 to 1.24.2 ([#1079](https://github.com/bitnami-labs/sealed-secrets/pull/1079)).\n- Bump k8s.io/code-generator from 0.25.4 to 0.26.0 ([#1078](https://github.com/bitnami-labs/sealed-secrets/pull/1078)).\n- Bump github.com/onsi/ginkgo/v2 from 2.6.0 to 2.6.1 ([#1077](https://github.com/bitnami-labs/sealed-secrets/pull/1077)).\n\n## v0.19.3\n\n### Changelog\n\n- Update to Go 1.19.4 ([#1073](https://github.com/bitnami-labs/sealed-secrets/pull/1073)).\n- Bump k8s.io/client-go from 0.25.4 to 0.26.0 ([#1071](https://github.com/bitnami-labs/sealed-secrets/pull/1071)).\n- Bump golang.org/x/crypto from 0.3.0 to 0.4.0 ([#1072](https://github.com/bitnami-labs/sealed-secrets/pull/1072)).\n- Bump github.com/onsi/ginkgo/v2 from 2.5.1 to 2.6.0 ([#1069](https://github.com/bitnami-labs/sealed-secrets/pull/1069)).\n- Bump k8s.io/api from 0.25.4 to 0.26.0 ([#1068](https://github.com/bitnami-labs/sealed-secrets/pull/1068)).\n- Bump golang.org/x/crypto from 0.2.0 to 0.3.0 ([#1063](https://github.com/bitnami-labs/sealed-secrets/pull/1063)).\n- Bump k8s.io/client-go from 0.25.3 to 0.25.4 ([#1062](https://github.com/bitnami-labs/sealed-secrets/pull/1062)).\n- Bump github.com/onsi/ginkgo/v2 from 2.5.0 to 2.5.1 ([#1061](https://github.com/bitnami-labs/sealed-secrets/pull/1061)).\n\n## v0.19.2\n\n### Changelog\n\n- Distinguish std & k8s errors ([#1046](https://github.com/bitnami-labs/sealed-secrets/pull/1046)).\n- Fix empty Group Version Kind ([#1044](https://github.com/bitnami-labs/sealed-secrets/pull/1044)).\n- Regenerate code - detected some dummy changes ([#1033](https://github.com/bitnami-labs/sealed-secrets/pull/1033)).\n- Decouple the kubeseal CLI from the kubeseal library ([#1030](https://github.com/bitnami-labs/sealed-secrets/pull/1030)).\n- Remove namespaceFn ([#1029](https://github.com/bitnami-labs/sealed-secrets/pull/1029)).\n\n## v0.19.1\n\n### Changelog\n\n- Fix release dockerhub container image name([#1014](https://github.com/bitnami-labs/sealed-secrets/pull/1014)).\n\n## v0.19.0\n\n### Changelog\n\n- FEATURE: Support to recreate a deleted secret generated by the controller([#963](https://github.com/bitnami-labs/sealed-secrets/pull/963)).\n- Update `golang.org/x/text` fixing CVE-2022-32149 ([#1008](https://github.com/bitnami-labs/sealed-secrets/pull/1008)).\n- Expired certificate error now prints expiration date in kubeseal([#986](https://github.com/bitnami-labs/sealed-secrets/pull/986)).\n\n## v0.18.5\n\n### Changelog\n\n- Fix `controller.yaml` having no image reference ([#977](https://github.com/bitnami-labs/sealed-secrets/pull/977))\n\n## v0.18.4\n\n### Changelog\n\n- Upgrade Go version, dependencies and fix CVE-2022-27664 ([#960](https://github.com/bitnami-labs/sealed-secrets/pull/960))\n- Move `kubeseal` to its own package ([#939](https://github.com/bitnami-labs/sealed-secrets/pull/939))\n- Several refactors to the `controller` ([#940](https://github.com/bitnami-labs/sealed-secrets/pull/940) & [#947](https://github.com/bitnami-labs/sealed-secrets/pull/947))\n- Generate a proper schema for the CRD ([#941](https://github.com/bitnami-labs/sealed-secrets/pull/941), [#957](https://github.com/bitnami-labs/sealed-secrets/pull/957), [#964](https://github.com/bitnami-labs/sealed-secrets/pull/964), [#966](https://github.com/bitnami-labs/sealed-secrets/pull/966) & [#970](https://github.com/bitnami-labs/sealed-secrets/pull/970))\n- Publish `kubeseal` in a container image ([#921](https://github.com/bitnami-labs/sealed-secrets/pull/921))\n\n## v0.18.3\n\nIncomplete release\n\n## v0.18.2\n\n### Changelog\n\n- Replace ioutil with io or os ([#895](https://github.com/bitnami-labs/sealed-secrets/pull/895))\n- Remove CLI global variables and refactor flag handling ([#901](https://github.com/bitnami-labs/sealed-secrets/pull/901) & [#920](https://github.com/bitnami-labs/sealed-secrets/pull/920))\n- Upgrade Go version, dependencies and tooling ([#904](https://github.com/bitnami-labs/sealed-secrets/pull/904) & [#905](https://github.com/bitnami-labs/sealed-secrets/pull/905))\n\n## v0.18.1\n\n### Changelog\n\n- Add flags to set the rate limit for the verify endpoint ([#873](https://github.com/bitnami-labs/sealed-secrets/pull/873))\n\n## v0.18.0\n\n### Changelog\n\n- Add capability to watch multiple namespaces ([#572](https://github.com/bitnami-labs/sealed-secrets/pull/572))\n- Bump `gopkg.in/yaml.v3` to avoid CVE-2022-28948 ([#852](https://github.com/bitnami-labs/sealed-secrets/pull/852))\n- Bump `prometheus/client_golang` and `crypto` dependencies to avoid CVE-2022-21698 and CVE-2022-27191 ([#831](https://github.com/bitnami-labs/sealed-secrets/pull/831))\n- Sign container images with cosign ([#810](https://github.com/bitnami-labs/sealed-secrets/pull/810) and [#851](https://github.com/bitnami-labs/sealed-secrets/pull/851))\n\n## v0.17.5\n\n### Changelog\n\n- Switch to dockerhub([#823](https://github.com/bitnami-labs/sealed-secrets/pull/823))\n- Sign the release using cosign ([#814](https://github.com/bitnami-labs/sealed-secrets/pull/814))\n\n## v0.17.4\n\n### Changelog\n\n- Fix linter errors running golangci-lint ([#751](https://github.com/bitnami-labs/sealed-secrets/pull/751))([#771](https://github.com/bitnami-labs/sealed-secrets/pull/771))\n- Added kubeseal support for darwin/arm64 ([#752](https://github.com/bitnami-labs/sealed-secrets/pull/752))\n- Bump prometheus/client_golang dependency to avoid CVE-2022-21698 ([#783](https://github.com/bitnami-labs/sealed-secrets/pull/783))\n\n## v0.17.3\n\n### Changelog\n\n- Unseal templates even when encryptedData is empty ([#653](https://github.com/bitnami-labs/sealed-secrets/pull/653))\n- Add new RBAC rules to make Sealed Secret compatible with K8s environments with RBAC enabled ([#715](https://github.com/bitnami-labs/sealed-secrets/pull/715))\n- Allow re-encrypt/validate functionalities to work with named ports defined in the Sealed Secret service ([#726](https://github.com/bitnami-labs/sealed-secrets/pull/726))\n- Fix verbose logging ([#727](https://github.com/bitnami-labs/sealed-secrets/pull/727))\n\n## v0.17.2\n\n### Changelog\n\n- Fix issue fetching the certificate when the Sealed Secrets service has a named port ([#648](https://github.com/bitnami-labs/sealed-secrets/pull/648))\n- Drop support for Go < 1.16 and bump client-go version ([#705](https://github.com/bitnami-labs/sealed-secrets/pull/705))\n\n## v0.17.1\n\n### Changelog\n\n- Binaries to emit the proper version ([#683](https://github.com/bitnami-labs/sealed-secrets/pull/683))\n- Re-enable publishing K8s manifests in GH releases ([#678](https://github.com/bitnami-labs/sealed-secrets/issues/678))\n\n## v0.17.0\n\n### Announcements\n\nThis release finally turns on the `update-status` feature flag that was introduced back in v0.12.0. The feature is considered stable (if it doesn't work for you, you can deactivate it by setting `SEALED_SECRETS_UPDATE_STATUS=0` in the controller manifest).\n\n### Changelog\n\n- Update rbac api version to `rbac.authorization.k8s.io/v1` ([#602](https://github.com/bitnami-labs/sealed-secrets/issues/602))\n- Enable `--update-status` by default ([#583](https://github.com/bitnami-labs/sealed-secrets/pull/583))\n\n## v0.16.0\n\n### Changelog\n\n- Add ability to template arbitrary data keys within resulting secrets ([#445](https://github.com/bitnami-labs/sealed-secrets/issues/445))\n- Fix status CRD in controller.yaml (backport from helm chart) ([#567](https://github.com/bitnami-labs/sealed-secrets/issues/567))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/26?closed=1\n\n## v0.15.0\n\nThis release contains a couple of fixes in the controller and manifests.\n\nNotable mention: You can give the `--update-status` (also available as env var `SEALED_SECRETS_UPDATE_STATUS=1`) feature flag another try. We'll turn it on by default in ~~the next release~~ v0.17.0.\n\n### Changelog\n\n- Remove '{}' in CRD schema properties so that ArgoCD doesn't get confused ([#529](https://github.com/bitnami-labs/sealed-secrets/issues/529))\n- Fix bug in status updates ([#223](https://github.com/bitnami-labs/sealed-secrets/issues/223))\n- Add label-selector to filter Sealed Secrets ([#521](https://github.com/bitnami-labs/sealed-secrets/issues/521))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/28?closed=1\n\n## v0.14.1\n\n### Changelog\n\n- Fixed `condition_info` prometheus metric disappearance ([#504](https://github.com/bitnami-labs/sealed-secrets/issues/504))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/27?closed=1\n\n## v0.14.0\n\n### Changelog\n\n- Updated CustomResourceDefinition to apiextensions.k8s.io/v1 ([#490](https://github.com/bitnami-labs/sealed-secrets/issues/490))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/19?closed=1\n\n## v0.13.1\n\n### Changelog\n\n- Make it easier to upgrade from ancient (pre v0.9.0) controllers ([#466](https://github.com/bitnami-labs/sealed-secrets/issues/466))\n- Prometheus: add namespace to unseal error metric ([#463](https://github.com/bitnami-labs/sealed-secrets/issues/463))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/17?closed=1\n\n## v0.12.6\n\n# Announcements\n\nThis release contains a fix for [CVE-2020-14040](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-14040), which could have opened the possibility for an attacker to cause a DoS on the sealed-secret controller (provided the attacker can cause the controller to process a malicious sealed secret resource).\n\n### Changelog\n\n- Fix CVE-2020-14040 ([#456](https://github.com/bitnami-labs/sealed-secrets/issues/456))\n- Don't require a namespace when using --raw and cluster-wide scope ([#451](https://github.com/bitnami-labs/sealed-secrets/issues/451))\n- Unregister Prometheus Gauges associated to removed SealedSecrets conditions ([#422](https://github.com/bitnami-labs/sealed-secrets/issues/422))\n- Add -f and -w flags as an alternative to stdin/out ([#439](https://github.com/bitnami-labs/sealed-secrets/issues/439))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/24?closed=1\n\n## v0.12.5\n\n### Changelog\n\n- Add `condition_info` metric to expose SealedSecrets status ([#421](https://github.com/bitnami-labs/sealed-secrets/issues/421))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/23?closed=1\n\n## v0.12.4\n\n### Announcements\n\nThe binaries in this release have been rebuilt with the Go 1.14.3 toolchain. No other changes in binaries nor k8s manifests.\n\n### Changelog\n\n- Build with latest Go 1.14.x version ([#411](https://github.com/bitnami-labs/sealed-secrets/issues/411))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/22?closed=1\n\n## v0.12.3\n\n### Announcements\n\nThis release contains only a change in the `kubeseal` binary since v0.12.2. No controller nor k8s manifest changes.\n\n### Changelog\n\n- Fix `--merge-into` file permissions on Windows ([#407](https://github.com/bitnami-labs/sealed-secrets/issues/407))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/21?closed=1\n\n## v0.12.2\n\n### Announcements\n\nThis release contains important changes in manifests since v0.12.1.\nIt also contains a minor fix in kubeseal client.\n\nPreviously, users upgrading to v0.12.x from previous versions would experience:\n\n```\nThe Deployment \"sealed-secrets-controller\" is invalid: spec.selector: Invalid value: v1.LabelSelector{MatchLabels:map[string]string{\"app.kubernetes.io/managed-by\":\"jsonnet\", \"app.kubernetes.io/name\":\"kubeseal\", \"app.kubernetes.io/part-of\":\"kubeseal\", \"app.kubernetes.io/version\":\"v0.12.1\", \"name\":\"sealed-secrets-controller\"}, MatchExpressions:[]v1.LabelSelectorRequirement(nil)}: field is immutable\n```\n\nThis was caused by a bug in our official yaml manifests introduced in v0.12.0. Users of the Helm chart were unaffected.\n\nBy reverting this issue we're are going to cause the same bad experience for users who did perform a clean install of v0.12.x.\nHowever, we believe such users are a minority.\n\n### Changelog\n\n- Revert \"Add recommended labels\" ([#404](https://github.com/bitnami-labs/sealed-secrets/issues/404))\n- remove kubeconfig deps from recovery-unseal ([#394](https://github.com/bitnami-labs/sealed-secrets/issues/394))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/19?closed=1\n\n## v0.12.1\n\n### Announcements\n\nThis release contains changes in `kubeseal` and `controller` binaries but no changes in manifests since v0.12.0.\n\nThis release is a fixup release that turns off the status update feature introduced in v0.12.0. Several users have reported\na severe bug (an infinite feedback loop where the controller kept updating SealedSecrets and consuming lots of CPU).\n\nIn order to turn it back on you need to manually pass the `--update-status` flag to the *controller* (or pass the `SEALED_SECRETS_UPDATE_STATUS=1` env var)\n\n### Changelog\n\n- Make it easier to use --raw from stdin ([#386](https://github.com/bitnami-labs/sealed-secrets/issues/386))\n- Deactivate status updates unless a feature flag is explicitly passed ([#388](https://github.com/bitnami-labs/sealed-secrets/issues/388))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/18?closed=1\n\n## v0.12.0\n\n### Announcements\n\nThis release contains changes in `kubeseal` and `controller` binaries as well as a minor change to the k8s manifest (see [#381](https://github.com/bitnami-labs/sealed-secrets/issues/381)); keep that in mind if you don't rely on the official k8s manifests, including the community-maintained Helm chart.\n\n# Status field\n\nNow the Sealed Secrets controller updates the `Status` field of the `SealedSecrets` resources.\nThis makes it easier for automation like ArgoCD to detect whether (and when) the controller has reacted to changes in the SealedSecret resources and produced a Secret. It also shows an error message in case it fails (many users are not familiar with k8s events and they may find it easier to see the error message in the status).\n\n# Prometheus\n\nThe Sealed Secrets controller now exports prometheus metrics. See also [contrib/prometheus-mixin](contrib/prometheus-mixin) and `controller-podmonitor.yaml`.\n\n### Changelog\n\n- Update Status field ([#346](https://github.com/bitnami-labs/sealed-secrets/issues/346))\n- Add prometheus metrics ([#177](https://github.com/bitnami-labs/sealed-secrets/issues/177))\n- Upgrade k8s client-go to v0.16.8 ([#380](https://github.com/bitnami-labs/sealed-secrets/issues/380))\n- kubeseal no longer emits empty `status: {}` field ([#383](https://github.com/bitnami-labs/sealed-secrets/issues/383))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/16?closed=1\n\n## v0.11.0\n\n### Announcements\n\nThis release contains only changes in kubeseal binary (no k8s manifest changes required).\n\n### For those who choose the name and namespace after sealing the secret\n\nCreating secrets with namespace-wide and cluster-wide scopes is now easier as it no longer requires manually adding annotations in the input Secret before passing it to `kubeseal`. This was often the root cause of many support requests. Now all you need to do is to:\n\n```\n$ kubeseal --scope namespace-wide <input-secret.yaml >output-sealed-secret.json\n```\n\n### Changelog\n\n- Honour --scope flag ([#371](https://github.com/bitnami-labs/sealed-secrets/issues/371))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/15?closed=1\n\n## v0.10.0\n\n### Announcements\n\nThis release supports the ARM 32 bit and 64 bit architectures, both on the client and the controller sides.\n\nWe also end the silly streak of patch level releases that actually contained features. We'll try to bump the minor version on every release except true hotfixes.\n\n### Changelog\n\n- Provide multi-arch Container image for Sealed Secrets controller ([#349](https://github.com/bitnami-labs/sealed-secrets/issues/349))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/2?closed=1\n\n## v0.9.8\n\n### Announcements\n\nThis release contains only changes in Linux `kubeseal-arm` and `kubeseal-arm64` binaries. There are no changes in the docker images, nor in the `x86_64` binaries for any of the supported OS.\n\n### Changelog\n\n- Fix bad release of Linux ARM7 and ARM64 binaries ([#362](https://github.com/bitnami-labs/sealed-secrets/issues/362))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/14?closed=1\n\n## v0.9.7\n\n### Announcements\n\nThis release contains  changes in `kubeseal` and `controller` binaries as well as a minor change to the k8s manifest (see [#338](https://github.com/bitnami-labs/sealed-secrets/issues/338)); keep that in mind if you don't rely on the official k8s manifests, including the community-maintained Helm chart.\n\n### Allow overwriting existing secrets\n\nBy default, the sealed-secrets controller doesn't unseal a SealedSecret over an existing Secret resource (i.e. a resource that has not been created by the sealed-secrets controller in the first place).\n\nThis is an important safeguard, not only to catch accidental overwrites due to typos etc, but also as a security measure: the sealed-secrets controller can create/update Secret resources even if the user who has the RBAC rights to create the SealedSecret resource doesn't have the right to create/update a Secret resource. We didn't want the sealed-secret controller to give its users more effective rights than what they would otherwise have without the sealed-secrets controller. A simple way to achieve that was permit only updates (overwrites) to Secret resources that were already owned by the sealed-secrets controller (which also seemed a sensible thing to do since it protects from accidental overwrites).\n\nHowever, this behavior gets in the way when you're just starting to use SealedSecrets and want to migrate your existing Secrets into SealedSecrets.\n\nYou now can just annotate your `Secret`s with `sealedsecrets.bitnami.com/managed: true` thus indicating that they can be safely overwritten by the sealed-secrets controller. This doesn't loosen our security model since you'd have to have RBAC rights to annotate the existing secrets (e.g. with `kubectl annotate`) or you can ask your friendly admins to do it on your behalf.\n\n### Changelog\n\n- Release includes ARMv7 and ARM64 binaries (although no docker images yet) ([#173](https://github.com/bitnami-labs/sealed-secrets/issues/173))\n- Set `fsGroup` to `nobody` in order to support `BoundServiceAccountTokenVolume` ([#338](https://github.com/bitnami-labs/sealed-secrets/issues/338))\n- Add `--force-empty-data` flag to allow (un)sealing an empty secret ([#334](https://github.com/bitnami-labs/sealed-secrets/issues/334))\n- Avoid forcing the default namespace when sealing a cluster-wide secret ([#323](https://github.com/bitnami-labs/sealed-secrets/issues/323))\n- Introduce the `sealedsecrets.bitnami.com/managed: true` annotation which controls overwriting existing secrets ([#331](https://github.com/bitnami-labs/sealed-secrets/issues/331))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/13?closed=1\n\n## v0.9.6\n\n### Announcements\n\nThis release contains only changes in `kubeseal` and `controller` binaries (no k8s manifest changes required).\n\n### Preliminary support for running multiple controllers\n\nIt always been possible in theory to run multiple controller instance in multiple namespaces,\neach with their own sealing encryption keys and thus each able to unseal secrets intended for it.\nHowever, doing so created a lot of noise in the logs, since each controller wouldn't know which\nsecrets are meant to be decryptable, but failed to decrypt, and which it ought to ignore.\n\nSince v0.9.6 you can reduce this noise by setting the `--all-namespaces` flag to false (also via the env var `SEALED_SECRETS_ALL_NAMESPACES=false`).\n\n### Changelog\n\n- Give an option to search only the current namespace ([#316](https://github.com/bitnami-labs/sealed-secrets/issues/316))\n- Support parsing multiple private keys in --recovery-private-key ([#325](https://github.com/bitnami-labs/sealed-secrets/issues/325))\n- Add klog flags so we can troubleshoot k8s client ([#320](https://github.com/bitnami-labs/sealed-secrets/issues/320))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/12?closed=1\n\n## v0.9.5\n\n### Announcements\n\nThis release contains only changes in `kubeseal` binary (no k8s manifest changes required).\n\n### Changelog\n\n- Improve error reporting in case of missing kubeconfig when inferring namespace ([#313](https://github.com/bitnami-labs/sealed-secrets/issues/313))\n- Teach kubeseal to decrypt using backed up secrets ([#312](https://github.com/bitnami-labs/sealed-secrets/issues/312))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/11?closed=1\n\n## v0.9.4\n\n### Announcements\n\nThis release contains only changes in `kubeseal` and `controller` binaries (no k8s manifest changes required).\n\n### Changelog\n\n- Remove tty warning in `--fetch-cert` (regression caused by #303 released in v0.9.3) ([#306](https://github.com/bitnami-labs/sealed-secrets/issues/306))\n- Implement `--recovery-unseal` to help with disaster recovery ([#307](https://github.com/bitnami-labs/sealed-secrets/issues/307))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/10?closed=1\n\n## v0.9.3\n\n### Announcements\n\nThis release contains only changes in `kubeseal` and `controller` binaries (no k8s manifest changes required).\n\n### Changelog\n\n- Implement `--key-cutoff-time` ([#299](https://github.com/bitnami-labs/sealed-secrets/issues/299))\n- Warn if stdin is a terminal ([#303](https://github.com/bitnami-labs/sealed-secrets/issues/303))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/9?closed=1\n\n## v0.9.2\n\n### Announcements\n\nThis release contains only changes in `kubeseal` and `controller` binaries (no k8s manifest changes required).\n\n### Periodic key renewal and offline certificates\n\nA few people have raised concerns of how will automatic key+certificate renewal affect the offline signing workflow.\nFirst, a clarification: nothing changed. You can keep using your old certificates; it's just that if you do, you won't benefit from the additional security given from the periodic key renewal.\n\nIn order to simplify the workflow for those who do want to benefit from the key renewal, but at the same time\ncannot access the target cluster (while not being completely offline), we offer a little feature that will help: `--cert` has learned to accept http(s) URLs. You can point it to a place where you serve up-to-date certificates for your clusters (tip/idea: you can expose the controller's cert.pem files with an Ingress).\n\n### Changelog\n\n- Accept URLs in `--cert` ([#281](https://github.com/bitnami-labs/sealed-secrets/issues/281))\n- Improve logs/events in case of decryption error ([#274](https://github.com/bitnami-labs/sealed-secrets/issues/274))\n- Reduce likelihood of name/namespace mismatch when using `--merge-into` ([#286](https://github.com/bitnami-labs/sealed-secrets/issues/286))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/8?closed=1\n\n## v0.9.1\n\n- Make manifests compatible with k8s 1.16.x ([#269](https://github.com/bitnami-labs/sealed-secrets/issues/269))\n- Fix non-strict scopes with --raw ([#276](https://github.com/bitnami-labs/sealed-secrets/issues/276))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/7?closed=1\n\n## v0.9.0\n\n## Announcement\n\n### Private key renewal\n\nThis release turns on an important security feature: a new private key will be now created every 30 days by default.\nExisting sealed-secrets resources will still be decrypted until the keys are manually phased out.\n\nYou can read more about this feature and the problem of **secret rotation** and how it interacts with Sealed Secrets in this [README section](https://github.com/bitnami-labs/sealed-secrets#secret-rotation) or in the original GH issue #137.\n\nThis feature alone is not technically a breaking change for people who use the offline workflow with `kubeseal --cert`, since old keys are not rotated out automatically. Users would be required to update their offline certs only when they purge old keys manually (we might introduce automatic purging in the future).\n\nThat said, to reap the benefits of key renewal, users of the offline workflow are encouraged to update their offline certificates every time a new key is generated (by default every 30 days).\n\n### Pre-v0.7.0 clients\n\nIf you are using kubeseal clients older than v0.7.0, please upgrade. Since this release the controller\nwill no longer accept the \"v1\" format of the encrypted \"data\" field and instead it will only support the\n\"encryptedData\" field.\n\nIf you have old sealed secret resources lying around, you can easily upgrade them by invoking:\n\n```bash\nkubeseal --re-encrypt <old.yaml >new.yaml\n```\n\n### Update items\n\nSince version v0.7.0 it was possible to update individual items in the `encryptedData` field of the Sealed Secret resource, but you had to manually copy&paste the encrypted items into an existing resource file. The required steps were never spelled out in the documentation and to be fair it always felt quite awkward.\n\nNow `kubectl` has learned how to update an existing secret, whilst preserving the same general operation principles, namely staying out of the business of actually crafting the secret itself (`kubectl create secret ...` and its various flags like `--from-file`, `--from-literal`, etc). Example:\n\n```bash\n$ kubectl create secret generic mysecret --dry-run -o json --from-file=foo=/tmp/foo \\\n  | kubeseal >sealed.json\n$ kubectl create secret generic mysecret --dry-run -o json --from-file=bar=/tmp/bar \\\n  | kubeseal --merge-into sealed.json\n```\n\n### Changelog\n\n- Doc improvements.\n- Rename \"key rotation\" to \"key renewal\" since the terminology was confusing.\n- Key renewal is enabled by default every 30 days ([#236](https://github.com/bitnami-labs/sealed-secrets/issues/236))\n- You can now use env vars such as SEALED_SECRETS_FOO_BAR to customize the controller ([#234](https://github.com/bitnami-labs/sealed-secrets/issues/234))\n- Deactivating by default deprecated \"v1\" encrypted data format (used by pre-v0.7.0 clients) ([#235](https://github.com/bitnami-labs/sealed-secrets/issues/235))\n- Fix RBAC rules for /v1/rotate and /v1/validate fixing #166 for good ([#249](https://github.com/bitnami-labs/sealed-secrets/issues/249))\n- Implement the --merge-into command ([#253](https://github.com/bitnami-labs/sealed-secrets/issues/253))\n- Add the `-o` alias for `--format` ([#261](https://github.com/bitnami-labs/sealed-secrets/issues/261))\n- Add the `--raw` command for only encrypting single items ([#257](https://github.com/bitnami-labs/sealed-secrets/issues/257))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/1?closed=1\n\n## v0.8.3\n\n### Announcements\n\nThis release contains a fix for a possible secret leak that can happen when sealing existing secrets that have been retrieved from a cluster (e.g. with `kubectl get`) where they have been created with `kubectl apply` (as opposed to `kubectl create`).\nThis potential problem has been introduced v0.8.0 when kubeseal learned how to preserve annotations and labels.\n\nPlease check your existing sealed secret sources for any annotation `kubectl.kubernetes.io/last-applied-configuration`, because that annotation would contain your original secrets in clear.\n\nThis release strips this annotation (and a similar annotation created by the `kubecfg` tool)\n\n### Changelog\n\nFixes in this release:\n\n- Round-tripping secrets can leak clear-text in last-applied-configuration ([#227](https://github.com/bitnami-labs/sealed-secrets/issues/227))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/6?closed=1\n\n## v0.8.2\n\nFixes in this release:\n\n- Endless loop in controller on invalid base64 encrypted data bug ([#201](https://github.com/bitnami-labs/sealed-secrets/issues/201))\n- Fix RBAC for /v1/cert.pem public key in isolated namespaces, removes most use cases for offline sealing with `--cert` ([#208](https://github.com/bitnami-labs/sealed-secrets/issues/208),[#166](https://github.com/bitnami-labs/sealed-secrets/issues/166))\n- Accept and seal stringData into secret ([#221](https://github.com/bitnami-labs/sealed-secrets/issues/221))\n- Fix a couple of blockers for enabling (still experimental) key rotation ([#185](https://github.com/bitnami-labs/sealed-secrets/issues/185), [#219](https://github.com/bitnami-labs/sealed-secrets/issues/219), [#218](https://github.com/bitnami-labs/sealed-secrets/issues/218))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/5?closed=1\n\n## v0.8.1\n\nFixes in this release:\n\n- Solve kubectl auth issues with clusters using `client.authentication.k8s.io/v1beta1` config by upgrading to client-go v12.0.0 ([#183](https://github.com/bitnami-labs/sealed-secrets/issues/183))\n- Fix controller crash when writing logs due to read-only root FS ([#200](https://github.com/bitnami-labs/sealed-secrets/issues/200))\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/4?closed=1\n\n## v0.8.0\n\nThe main improvements in this release are:\n\n- support for annotations and labels ([#92](https://github.com/bitnami-labs/sealed-secrets/issues/92))\n- support for secrets rotation opt-in ([#137](https://github.com/bitnami-labs/sealed-secrets/issues/137))\n- fix bug with OwnerReferences handling ([#127](https://github.com/bitnami-labs/sealed-secrets/issues/127))\n- EKS support; client-go version bump to release-7.0 ([#110](https://github.com/bitnami-labs/sealed-secrets/issues/110))\n- Instructions to run on GKE when user is not cluster-admin ([#111](https://github.com/bitnami-labs/sealed-secrets/issues/111))\n- Windows binary of kubeseal ([#85](https://github.com/bitnami-labs/sealed-secrets/issues/85))\n- Internal codebase modernization (e.g. switch to Go modules)\n\nThe full Changelog is maintained in https://github.com/bitnami-labs/sealed-secrets/milestone/3?closed=1\n\nMany thanks for all the folks who contributed to this release!\n\n## v0.7.0\n\nBig change for this release is the switch to **per-key encrypted values**.\n\n- (\"Keys\" as in \"object key/value\", not as in \"encryption key\".  English is hard.)*\n- Previously we generated a single big encrypted blob for each Secret, now we encrypt each value in the Secret separately, with the keys in plain text. This allows:\n  - Existing keys can now be renamed and deleted without re-encrypting the value(s).\n  - New keys/values can be added to the SealedSecret without re-encrypting (or even having access to!) the existing values.\n  - Note that (as before) the encrypted values are still tied to the namespace/name of the enclosing Secret/SealedSecret, so can't be moved to another Secret.\n   (The [cluster-wide annotation](https://github.com/bitnami-labs/sealed-secrets/blob/bda0af6a6a8abebc9ff359dd2e5e22d54cb40798/pkg/apis/sealed-secrets/v1alpha1/types.go#L16)  _does_ allow this, with the corresponding caveats, as before)\n- The `kubeseal` tool does not yet have an option to output _just_ a single value, but you can safely mix+match the individual values from `kubeseal` output with an existing SealedSecret.  Improving `kubeseal` support for this feature is still an open action item.\n- Existing/older \"all-in-one\" SealedSecrets are declared deprecated, but will continue to be supported by the controller for the foreseeable future.  New invocations of the `kubeseal` tool now produce per-key encrypted output - if you need to produce the older format, just use an older `kubeseal`.  Please raise a github issue if you have a use-case that requires supporting \"all-in-one\" SealedSecrets going forward.\n- Note the CRD schema used for server-side validation in k8s >=1.9 has been temporarily removed, because it was unable to support the new per-key structure correctly (see [kubernetes/kubernetes#59485](https://github.com/kubernetes/kubernetes/issues/59485)).\n- Huge thanks to @sullerandras for the code and his persistence in getting this merged!\n\n## v0.6.0\n\n- Support \"cluster wide\" secrets, that are not restricted to the original namespace\n  - Set `sealedsecrets.bitnami.com/cluster-wide: \"true\"` annotation\n  - Warning: cluster-wide SealedSecrets can be decrypted by anyone who can create a SealedSecret in your cluster\n- Move to client-go v5.0\n- Move to bitnami-labs github org\n- Fix bug in schema validation for k8s 1.9\n\n## v0.5.1\n\n**Note:** this version moves TPR/CRD definition into a separate file.  To install, you need `controller.yaml` *and* either `sealedsecret-tpr.yaml` or `sealedsecret-crd.yaml`\n\n- Add CRD definition and TPR->CRD migration documentation\n- Add `kubeseal --fetch-cert` to dump server cert to stdout, for later offline use with `kubeseal --cert`\n- Better sanitization of input object to `kubeseal`\n\n(v0.5.1 fixes a travis/github release issue with v0.5.0)\n\n## v0.5.0\n\n## v0.4.0\n\n- controller: deployment security hardening: non-root uid and read-only rootfs\n- `kubeseal`: Include oidc and gcp auth provider plugins\n- `kubeseal`: Add support for YAML output\n\n## v0.3.1\n\n- Add `controller-norbac.yaml` to the release build. This is `controller.yaml` without RBAC rules and related service account - for environments where RBAC is not yet supported, [like Azure](https://github.com/Azure/acs-engine/issues/680).\n- Fix missing controller RBAC ClusterRoleBinding in v0.3.0\n\n## v0.3.0\n\nRename everything to better represent project scope.  Better to do this early (now) and apologies for the disruption.\n\n- Rename repo and golang import path -> `bitnami/sealed-secrets`\n- Rename cli tool -> `kubeseal`\n- Rename `SealedSecret` apiGroup -> `bitnami.com`\n\n## v0.2.1\n\n- Fix invalid field `resourceName` in v0.2.0 controller.yaml (thanks @Globegitter)\n\n## v0.2.0\n\n- Client tool has better defaults, and can fetch the certificate automatically from the controller.\n- Improve release process to include pre-built Linux and OSX x86-64 binaries.\n\n## v0.1.0\n\nBasic functionality is complete.\n\n## v0.0.1\n\n- Clean up controller.jsonnet\n- Switch to quay.io (docker hub doesn't offer robot accounts??)\n- Add deploy section to .travis.yml\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Release Process\n\nThe community has adopted this security disclosure and response policy to ensure we responsibly handle critical issues.\n\n## Supported Versions\n\nFor a list of support versions that this project will potentially create security fixes for, please refer to the [Releases page](https://github.com/bitnami-labs/sealed-secrets/blob/main/CONTRIBUTING.md#release-process) on this project's GitHub and/or project related documentation on release cadence and support.\n\n## Reporting a Vulnerability - Private Disclosure Process\n\nSecurity is of the highest importance and all security vulnerabilities or suspected security vulnerabilities should be reported to this project privately, to minimize attacks against current users  before they are fixed. Vulnerabilities will be investigated and patched on the next patch (or minor) release as soon as possible. This information could be kept entirely internal to the project.\n\nIf you know of a publicly disclosed security vulnerability for this project, please **IMMEDIATELY** contact the [maintainers](mailto:sealed-secrets.pdl@broadcom.com) of this project privately. The use of encrypted email is encouraged.\n\n>**IMPORTANT:** Do not file public issues on GitHub for security vulnerabilities\n\nTo report a vulnerability or a security-related issue, please contact the maintainers with enough details through one of the following channels:\n\n* Directly via the team email addresses (sealed-secrets.pdl@broadcom.com)\n* Open a [GitHub Security Advisory](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability). This allows for anyone to report security vulnerabilities directly and privately to the maintainers via GitHub. Note that this option may not be present for every repository.\n\nThe report will be fielded by the [maintainers](https://github.com/bitnami-labs/sealed-secrets/blob/main/MAINTAINERS.md) who have committer and release permissions. Feedback will be sent within 3 business days, including a detailed plan to investigate the issue and any potential workarounds to perform in the meantime.\n\nDo not report non-security-impacting bugs through this channel. Use GitHub issues for all non-security-impacting bugs.\n\n## Proposed Report Content\n\nProvide a descriptive title and in the description of the report include the following information:\n\n* Basic identity information, such as your name and your affiliation or company.\n* Detailed steps to reproduce the vulnerability  (POC scripts, screenshots, and logs are all helpful to us).\n* Description of the effects of the vulnerability on this project and the related hardware and software configurations, so that the maintainers can reproduce it.\n* How the vulnerability affects this project's usage and an estimation of the attack surface, if there is one.\n* List other projects or dependencies that were used in conjunction with this project to produce the vulnerability.\n\n## When to report a vulnerability\n\n* When you think this project has a potential security vulnerability.\n* When you suspect a potential vulnerability but you are unsure that it impacts this project.\n* When you know of or suspect a potential vulnerability on another project that is used by this project.\n\n## Patch, Release, and Disclosure\n\nThe maintainers will respond to vulnerability reports as follows:\n\n1. The maintainers will investigate the vulnerability and determine its effects and criticality.\n2. If the issue is not deemed to be a vulnerability, the maintainers will follow up with a detailed reason for rejection.\n3. The maintainers will initiate a conversation with the reporter within 3 business days.\n4. If a vulnerability is acknowledged and the timeline for a fix is determined, the maintainers will work on a plan to communicate with the appropriate community, including identifying mitigating steps that affected users can take to protect themselves until the fix is rolled out.\n5. The maintainers will also create a [Security Advisory](https://docs.github.com/en/code-security/repository-security-advisories/publishing-a-repository-security-advisory) using the [CVSS Calculator](https://www.first.org/cvss/calculator/3.0), if it is not created yet.  The maintainers make the final call on the calculated CVSS; it is better to move quickly than making the CVSS perfect. Issues may also be reported to [Mitre](https://cve.mitre.org/) using this [scoring calculator](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator). The draft advisory will initially be set to private.\n6. The maintainers will work on fixing the vulnerability and perform internal testing before preparing to roll out the fix.\n7. Once the fix is confirmed, the maintainers will patch the vulnerability in the next patch or minor release, and backport a patch release into all earlier supported releases.\n\n## Public Disclosure Process\n\nThe maintainers publish the public advisory to this project's community via GitHub. In most cases, additional communication via Slack, Twitter, mailing lists, blog, and other channels will assist in educating the project's users and rolling out the patched release to affected users.\n\nThe maintainers will also publish any mitigating steps users can take until the fix can be applied to their instances. This project's distributors will handle creating and publishing their own security advisories.\n\n## Confidentiality, integrity and availability\n\nWe consider vulnerabilities leading to the compromise of data confidentiality, elevation of privilege, or integrity to be our highest priority concerns. Availability, in particular in areas relating to DoS and resource exhaustion, is also a serious security concern. The maintainer team takes all vulnerabilities, potential vulnerabilities, and suspected vulnerabilities seriously and will investigate them in an urgent and expeditious manner.\n\nNote that we do not currently consider the default settings for this project to be secure-by-default. It is necessary for operators to explicitly configure settings, role based access control, and other resource related features in this project to provide a hardened environment. We will not act on any security disclosure that relates to a lack of safe defaults. Over time, we will work towards improved safe-by-default configuration, taking into account backwards compatibility.\n"
  },
  {
    "path": "carvel/package.yaml",
    "content": "apiVersion: data.packaging.carvel.dev/v1alpha1\nkind: Package\nmetadata:\n  name: \"sealedsecrets.bitnami.com.2.18.1\"\nspec:\n  refName: \"sealedsecrets.bitnami.com\"\n  version: \"2.18.1\"\n  valuesSchema:\n    openAPIv3:\n      title: Chart Values\n      type: object\n      properties:\n        kubeVersion:\n          type: string\n          description: Override Kubernetes version\n          default: \"\"\n        nameOverride:\n          type: string\n          description: String to partially override sealed-secrets.fullname\n          default: \"\"\n        fullnameOverride:\n          type: string\n          description: String to fully override sealed-secrets.fullname\n          default: \"\"\n        namespace:\n          type: string\n          description: Namespace where to deploy the Sealed Secrets controller\n          default: \"\"\n        extraDeploy:\n          type: array\n          description: Array of extra objects to deploy with the release\n          default: []\n          items: {}\n        image:\n          type: object\n          properties:\n            registry:\n              type: string\n              description: Sealed Secrets image registry\n              default: docker.io\n            repository:\n              type: string\n              description: Sealed Secrets image repository\n              default: bitnami/sealed-secrets-controller\n            tag:\n              type: string\n              description: Sealed Secrets image tag (immutable tags are recommended)\n              default: v0.24.5\n            pullPolicy:\n              type: string\n              description: Sealed Secrets image pull policy\n              default: IfNotPresent\n            pullSecrets:\n              type: array\n              description: Sealed Secrets image pull secrets\n              default: []\n              items: {}\n        createController:\n          type: boolean\n          description: Specifies whether the Sealed Secrets controller should be created\n          default: true\n        secretName:\n          type: string\n          description: The name of an existing TLS secret containing the key used to encrypt secrets\n          default: sealed-secrets-key\n        updateStatus:\n          type: boolean\n          description: Specifies whether the Sealed Secrets controller should update the status subresource\n          default: true\n        skipRecreate:\n          type: boolean\n          description: Specifies whether the Sealed Secrets controller should skip recreating removed secrets\n          default: false\n        keyrenewperiod:\n          type: string\n          description: Specifies key renewal period. Default 30 days\n          default: \"\"\n        rateLimit:\n          type: string\n          description: Number of allowed sustained request per second for verify endpoint\n          default: \"\"\n        rateLimitBurst:\n          type: string\n          description: Number of requests allowed to exceed the rate limit per second for verify endpoint\n          default: \"\"\n        additionalNamespaces:\n          type: array\n          description: List of namespaces used to manage the Sealed Secrets\n          default: []\n          items: {}\n        command:\n          type: array\n          description: Override default container command\n          default: []\n          items: {}\n        args:\n          type: array\n          description: Override default container args\n          default: []\n          items: {}\n        livenessProbe:\n          type: object\n          properties:\n            enabled:\n              type: boolean\n              description: Enable livenessProbe on Sealed Secret containers\n              default: true\n            initialDelaySeconds:\n              type: number\n              description: Initial delay seconds for livenessProbe\n              default: 0\n            periodSeconds:\n              type: number\n              description: Period seconds for livenessProbe\n              default: 10\n            timeoutSeconds:\n              type: number\n              description: Timeout seconds for livenessProbe\n              default: 1\n            failureThreshold:\n              type: number\n              description: Failure threshold for livenessProbe\n              default: 3\n            successThreshold:\n              type: number\n              description: Success threshold for livenessProbe\n              default: 1\n        readinessProbe:\n          type: object\n          properties:\n            enabled:\n              type: boolean\n              description: Enable readinessProbe on Sealed Secret containers\n              default: true\n            initialDelaySeconds:\n              type: number\n              description: Initial delay seconds for readinessProbe\n              default: 0\n            periodSeconds:\n              type: number\n              description: Period seconds for readinessProbe\n              default: 10\n            timeoutSeconds:\n              type: number\n              description: Timeout seconds for readinessProbe\n              default: 1\n            failureThreshold:\n              type: number\n              description: Failure threshold for readinessProbe\n              default: 3\n            successThreshold:\n              type: number\n              description: Success threshold for readinessProbe\n              default: 1\n        startupProbe:\n          type: object\n          properties:\n            enabled:\n              type: boolean\n              description: Enable startupProbe on Sealed Secret containers\n              default: false\n            initialDelaySeconds:\n              type: number\n              description: Initial delay seconds for startupProbe\n              default: 0\n            periodSeconds:\n              type: number\n              description: Period seconds for startupProbe\n              default: 10\n            timeoutSeconds:\n              type: number\n              description: Timeout seconds for startupProbe\n              default: 1\n            failureThreshold:\n              type: number\n              description: Failure threshold for startupProbe\n              default: 3\n            successThreshold:\n              type: number\n              description: Success threshold for startupProbe\n              default: 1\n        customLivenessProbe:\n          type: object\n          description: Custom livenessProbe that overrides the default one\n          default: {}\n        customReadinessProbe:\n          type: object\n          description: Custom readinessProbe that overrides the default one\n          default: {}\n        customStartupProbe:\n          type: object\n          description: Custom startupProbe that overrides the default one\n          default: {}\n        podSecurityContext:\n          type: object\n          properties:\n            enabled:\n              type: boolean\n              description: Enabled Sealed Secret pods' Security Context\n              default: true\n            fsGroup:\n              type: number\n              description: Set Sealed Secret pod's Security Context fsGroup\n              default: 65534\n        containerSecurityContext:\n          type: object\n          properties:\n            enabled:\n              type: boolean\n              description: Enabled Sealed Secret containers' Security Context\n              default: true\n            readOnlyRootFilesystem:\n              type: boolean\n              description: Whether the Sealed Secret container has a read-only root filesystem\n              default: true\n            runAsNonRoot:\n              type: boolean\n              description: Indicates that the Sealed Secret container must run as a non-root user\n              default: true\n            runAsUser:\n              type: number\n              description: Set Sealed Secret containers' Security Context runAsUser\n              default: 1001\n        automountServiceAccountToken:\n          type: string\n          description: whether to automatically mount the service account API-token to a particular pod\n          default: \"\"\n        priorityClassName:\n          type: string\n          description: Sealed Secret pods' priorityClassName\n          default: \"\"\n        runtimeClassName:\n          type: string\n          description: Sealed Secret pods' runtimeClassName\n          default: \"\"\n        tolerations:\n          type: array\n          description: Tolerations for Sealed Secret pods assignment\n          default: []\n          items: {}\n        hostNetwork:\n          type: boolean\n          description: Sealed Secrets pods' hostNetwork\n          default: false\n        dnsPolicy:\n          type: string\n          description: Sealed Secrets pods' dnsPolicy\n          default: \"\"\n        service:\n          type: object\n          properties:\n            type:\n              type: string\n              description: Sealed Secret service type\n              default: ClusterIP\n            port:\n              type: number\n              description: Sealed Secret service HTTP port\n              default: 8080\n            nodePort:\n              type: string\n              description: Node port for HTTP\n              default: \"\"\n        ingress:\n          type: object\n          properties:\n            enabled:\n              type: boolean\n              description: Enable ingress record generation for Sealed Secret\n              default: false\n            pathType:\n              type: string\n              description: Ingress path type\n              default: ImplementationSpecific\n            apiVersion:\n              type: string\n              description: Force Ingress API version (automatically detected if not set)\n              default: \"\"\n            ingressClassName:\n              type: string\n              description: IngressClass that will be be used to implement the Ingress\n              default: \"\"\n            hostname:\n              type: string\n              description: Default host for the ingress record\n              default: sealed-secrets.local\n            path:\n              type: string\n              description: Default path for the ingress record\n              default: /v1/cert.pem\n            tls:\n              type: boolean\n              description: Enable TLS configuration for the host defined at `ingress.hostname` parameter\n              default: false\n            selfSigned:\n              type: boolean\n              description: Create a TLS secret for this ingress record using self-signed certificates generated by Helm\n              default: false\n            extraHosts:\n              type: array\n              description: An array with additional hostname(s) to be covered with the ingress record\n              default: []\n              items: {}\n            extraPaths:\n              type: array\n              description: An array with additional arbitrary paths that may need to be added to the ingress under the main host\n              default: []\n              items: {}\n            extraTls:\n              type: array\n              description: TLS configuration for additional hostname(s) to be covered with this ingress record\n              default: []\n              items: {}\n            secrets:\n              type: array\n              description: Custom TLS certificates as secrets\n              default: []\n              items: {}\n        networkPolicy:\n          type: object\n          properties:\n            enabled:\n              type: boolean\n              description: Specifies whether a NetworkPolicy should be created\n              default: false\n        serviceAccount:\n          type: object\n          properties:\n            create:\n              type: boolean\n              description: Specifies whether a ServiceAccount should be created\n              default: true\n            labels:\n              type: object\n              description: Extra labels to be added to the ServiceAccount\n              default: {}\n            name:\n              type: string\n              description: The name of the ServiceAccount to use.\n              default: \"\"\n            automountServiceAccountToken:\n              type: string\n              description: Specifies, whether to mount the service account API-token\n              default: \"\"\n        rbac:\n          type: object\n          properties:\n            create:\n              type: boolean\n              description: Specifies whether RBAC resources should be created\n              default: true\n            clusterRole:\n              type: boolean\n              description: Specifies whether the Cluster Role resource should be created\n              default: true\n            labels:\n              type: object\n              description: Extra labels to be added to RBAC resources\n              default: {}\n            pspEnabled:\n              type: boolean\n              description: PodSecurityPolicy\n              default: false\n        metrics:\n          type: object\n          properties:\n            serviceMonitor:\n              type: object\n              properties:\n                enabled:\n                  type: boolean\n                  description: Specify if a ServiceMonitor will be deployed for Prometheus Operator\n                  default: false\n                namespace:\n                  type: string\n                  description: Namespace where Prometheus Operator is running in\n                  default: \"\"\n                labels:\n                  type: object\n                  description: Extra labels for the ServiceMonitor\n                  default: {}\n                annotations:\n                  type: object\n                  description: Extra annotations for the ServiceMonitor\n                  default: {}\n                interval:\n                  type: string\n                  description: How frequently to scrape metrics\n                  default: \"\"\n                scrapeTimeout:\n                  type: string\n                  description: Timeout after which the scrape is ended\n                  default: \"\"\n                honorLabels:\n                  type: boolean\n                  description: Specify if ServiceMonitor endPoints will honor labels\n                  default: true\n                metricRelabelings:\n                  type: array\n                  description: Specify additional relabeling of metrics\n                  default: []\n                  items: {}\n                relabelings:\n                  type: array\n                  description: Specify general relabeling\n                  default: []\n                  items: {}\n            dashboards:\n              type: object\n              properties:\n                create:\n                  type: boolean\n                  description: Specifies whether a ConfigMap with a Grafana dashboard configuration should be created\n                  default: false\n                labels:\n                  type: object\n                  description: Extra labels to be added to the Grafana dashboard ConfigMap\n                  default: {}\n                namespace:\n                  type: string\n                  description: Namespace where Grafana dashboard ConfigMap is deployed\n                  default: \"\"\n  template:\n    spec:\n      fetch:\n        - imgpkgBundle:\n            image: ghcr.io/bitnami-labs/sealed-secrets-carvel@sha256:9dd602e7653ef7979a67eeab60bd58fe1059de8cc208d40d5293279bd80f6478\n      template:\n        - helmTemplate:\n            path: sealed-secrets\n        - kbld:\n            paths:\n              - \"-\"\n              - .imgpkg/images.yml\n      deploy:\n        - kapp: {}\n"
  },
  {
    "path": "cmd/controller/main.go",
    "content": "package main\n\nimport (\n\tgoflag \"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"os\"\n\t\"time\"\n\n\tflag \"github.com/spf13/pflag\"\n\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/controller\"\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/flagenv\"\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/log\"\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/pflagenv\"\n\n\tssv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/apis/sealedsecrets/v1alpha1\"\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/buildinfo\"\n)\n\nconst (\n\tflagEnvPrefix           = \"SEALED_SECRETS\"\n\tdefaultKeyRenewPeriod   = 30 * 24 * time.Hour\n\tdefaultKeyOrderPriority = \"CertNotBefore\"\n)\n\nvar (\n\t// VERSION set from Makefile.\n\tVERSION = buildinfo.DefaultVersion\n)\n\nfunc bindControllerFlags(f *controller.Flags, fs *flag.FlagSet) {\n\tfs.StringVar(&f.KeyPrefix, \"key-prefix\", \"sealed-secrets-key\", \"Prefix used to name keys.\")\n\tfs.IntVar(&f.KeySize, \"key-size\", 4096, \"Size of encryption key.\")\n\tfs.DurationVar(&f.ValidFor, \"key-ttl\", 10*365*24*time.Hour, \"Duration that certificate is valid for.\")\n\tfs.StringVar(&f.MyCN, \"my-cn\", \"\", \"Common name to be used as issuer/subject DN in generated certificate.\")\n\n\tfs.DurationVar(&f.KeyRenewPeriod, \"key-renew-period\", defaultKeyRenewPeriod, \"New key generation period (automatic rotation deactivated if 0)\")\n\tfs.StringVar(&f.KeyOrderPriority, \"key-order-priority\", defaultKeyOrderPriority, \"Ordering of keys based on NotBefore certificate attribute or secret creation timestamp.\")\n\tfs.BoolVar(&f.AcceptV1Data, \"accept-deprecated-v1-data\", true, \"Accept deprecated V1 data field.\")\n\tfs.StringVar(&f.KeyCutoffTime, \"key-cutoff-time\", \"\", \"Create a new key if latest one is older than this cutoff time. RFC1123 format with numeric timezone expected.\")\n\tfs.BoolVar(&f.NamespaceAll, \"all-namespaces\", true, \"Scan all namespaces or only the current namespace (default=true).\")\n\tfs.StringVar(&f.AdditionalNamespaces, \"additional-namespaces\", \"\", \"Comma-separated list of additional namespaces to be scanned.\")\n\tfs.StringVar(&f.LabelSelector, \"label-selector\", \"\", \"Label selector which can be used to filter sealed secrets.\")\n\tfs.IntVar(&f.RateLimitPerSecond, \"rate-limit\", 2, \"Number of allowed sustained request per second for verify endpoint\")\n\tfs.IntVar(&f.RateLimitBurst, \"rate-limit-burst\", 2, \"Number of requests allowed to exceed the rate limit per second for verify endpoint\")\n\tfs.StringVar(&f.PrivateKeyAnnotations, \"privatekey-annotations\", \"\", \"Comma-separated list of additional annotations to be put on renewed sealing keys.\")\n\tfs.StringVar(&f.PrivateKeyLabels, \"privatekey-labels\", \"\", \"Comma-separated list of additional labels to be put on renewed sealing keys.\")\n\n\tfs.BoolVar(&f.OldGCBehavior, \"old-gc-behavior\", false, \"Revert to old GC behavior where the controller deletes secrets instead of delegating that to k8s itself.\")\n\n\tfs.BoolVar(&f.UpdateStatus, \"update-status\", true, \"beta: if true, the controller will update the status sub-resource whenever it processes a sealed secret\")\n\tfs.BoolVar(&f.WatchForSecrets, \"watch-for-secrets\", false, \"beta: If this is true, the controller will watch for key secrets. This is useful if you create the key secrets externally.\")\n\n\tfs.BoolVar(&f.SkipRecreate, \"skip-recreate\", false, \"if true the controller will skip listening for managed secret changes to recreate them. This helps on limited permission environments.\")\n\n\tfs.BoolVar(&f.LogInfoToStdout, \"log-info-stdout\", false, \"if true the controller will log info to stdout.\")\n\tfs.StringVar(&f.LogLevel, \"log-level\", \"INFO\", \"Log level (INFO|ERROR).\")\n\tfs.StringVar(&f.LogFormat, \"log-format\", \"text\", \"Log format (text|json).\")\n\n\tfs.DurationVar(&f.KeyRenewPeriod, \"rotate-period\", defaultKeyRenewPeriod, \"\")\n\t_ = fs.MarkDeprecated(\"rotate-period\", \"please use key-renew-period instead\")\n\n\tfs.IntVar(&f.MaxRetries, \"max-unseal-retries\", 5, \"Max unseal retries.\")\n\n\tfs.Float32Var(&f.KubeClientQPS, \"kubeclient-qps\", 5, \"Kubeclient QPS (negative value disables ratelimiting)\")\n\tfs.IntVar(&f.KubeClientBurst, \"kubeclient-burst\", 10, \"Kubeclient Burst\")\n}\n\nfunc bindFlags(f *controller.Flags, fs *flag.FlagSet, gofs *goflag.FlagSet) {\n\tbindControllerFlags(f, fs)\n\n\tflagenv.SetFlagsFromEnv(flagEnvPrefix, gofs)\n\tpflagenv.SetFlagsFromEnv(flagEnvPrefix, fs)\n\n\t// Standard goflags (glog in particular)\n\tfs.AddGoFlagSet(gofs)\n\tif f := fs.Lookup(\"logtostderr\"); f != nil {\n\t\tf.DefValue = \"true\"\n\t\t_ = f.Value.Set(f.DefValue)\n\t}\n}\n\nfunc mainE(w io.Writer, fs *flag.FlagSet, gofs *goflag.FlagSet, args []string) error {\n\tvar printVersion bool\n\tvar flags controller.Flags\n\n\tbuildinfo.FallbackVersion(&VERSION, buildinfo.DefaultVersion)\n\tfs.BoolVar(&printVersion, \"version\", false, \"Print version information and exit\")\n\tbindFlags(&flags, fs, gofs)\n\tif err := fs.Parse(args); err != nil {\n\t\treturn err\n\t}\n\tif err := gofs.Parse([]string{}); err != nil {\n\t\treturn err\n\t}\n\n\t// Set logging\n\tlogLevel := slog.Level(0)\n\t_ = logLevel.UnmarshalText([]byte(flags.LogLevel))\n\topts := &slog.HandlerOptions{\n\t\tLevel: logLevel,\n\t}\n\tif flags.LogInfoToStdout {\n\t\tslog.SetDefault(slog.New(log.New(os.Stdout, os.Stderr, flags.LogFormat, opts)))\n\t} else {\n\t\tslog.SetDefault(slog.New(log.New(os.Stderr, os.Stderr, flags.LogFormat, opts)))\n\t}\n\n\tssv1alpha1.AcceptDeprecatedV1Data = flags.AcceptV1Data\n\n\tif printVersion {\n\t\tfmt.Fprintf(w, \"controller version: %s\\n\", VERSION)\n\t\treturn nil\n\t}\n\n\tslog.Info(\"Starting sealed-secrets controller\", \"version\", VERSION)\n\tif err := controller.Main(&flags, VERSION); err != nil {\n\t\tpanic(err)\n\t}\n\treturn nil\n}\n\nfunc main() {\n\tif err := mainE(os.Stdout, flag.CommandLine, goflag.CommandLine, os.Args); err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"error: %v\\n\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "cmd/controller/main_test.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\tgoflag \"flag\"\n\t\"testing\"\n\n\tflag \"github.com/spf13/pflag\"\n)\n\nfunc TestVersion(t *testing.T) {\n\tbuf := bytes.NewBufferString(\"\")\n\ttestVersionFlags := flag.NewFlagSet(\"testVersionFlags\", flag.ExitOnError)\n\ttestNopFlags := goflag.NewFlagSet(\"nop\", goflag.ExitOnError)\n\terr := mainE(buf, testVersionFlags, testNopFlags, []string{\"--version\"})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif got, want := buf.String(), \"controller version: UNKNOWN\\n\"; got != want {\n\t\tt.Errorf(\"got: %q, want: %q\", got, want)\n\t}\n}\n"
  },
  {
    "path": "cmd/kubeseal/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\n\tgoflag \"flag\"\n\n\tssv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/apis/sealedsecrets/v1alpha1\"\n\t\"github.com/google/renameio\"\n\t\"github.com/mattn/go-isatty\"\n\tflag \"github.com/spf13/pflag\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\t\"k8s.io/klog/v2\"\n\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/buildinfo\"\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/flagenv\"\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/kubeseal\"\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/pflagenv\"\n\n\t// Register Auth providers.\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth\"\n)\n\nconst (\n\tflagEnvPrefix = \"SEALED_SECRETS\"\n)\n\nvar (\n\t// VERSION set from Makefile.\n\tVERSION = buildinfo.DefaultVersion\n)\n\ntype cliFlags struct {\n\tcertURL        string\n\tcontrollerNs   string\n\tcontrollerName string\n\toutputFormat   string\n\toutputFileName string\n\tinputFileName  string\n\tkubeconfig     string\n\tdumpCert       bool\n\tallowEmptyData bool\n\tvalidateSecret bool\n\tmergeInto      string\n\traw            bool\n\tsecretName     string\n\tfromFile       []string\n\tsealingScope   ssv1alpha1.SealingScope\n\treEncrypt      bool\n\tunseal         bool\n\tprivKeys       []string\n\thelp           bool\n}\n\ntype config struct {\n\tflags        *cliFlags\n\tclientConfig kubeseal.ClientConfig\n\tctx          context.Context\n}\n\nfunc newConfig(clientConfig clientcmd.ClientConfig, flags *cliFlags) *config {\n\treturn &config{\n\t\tflags:        flags,\n\t\tclientConfig: clientConfig,\n\t\tctx:          context.Background(),\n\t}\n}\n\nfunc initClient(kubeConfigPath string, cfgOverrides *clientcmd.ConfigOverrides, r io.Reader) clientcmd.ClientConfig {\n\tloadingRules := clientcmd.NewDefaultClientConfigLoadingRules()\n\tloadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig\n\tloadingRules.ExplicitPath = kubeConfigPath\n\treturn clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, cfgOverrides, r)\n}\n\nfunc bindFlags(f *cliFlags, fs *flag.FlagSet) {\n\t// TODO: Verify k8s server signature against cert in kube client config.\n\tfs.StringVar(&f.certURL, \"cert\", \"\", \"Certificate / public key file/URL to use for encryption. Overrides --controller-*\")\n\tfs.StringVar(&f.controllerNs, \"controller-namespace\", metav1.NamespaceSystem, \"Namespace of sealed-secrets controller.\")\n\tfs.StringVar(&f.controllerName, \"controller-name\", \"sealed-secrets-controller\", \"Name of sealed-secrets controller.\")\n\tfs.StringVarP(&f.outputFormat, \"format\", \"o\", \"json\", \"Output format for sealed secret. Either json or yaml\")\n\tfs.StringVarP(&f.outputFileName, \"sealed-secret-file\", \"w\", \"\", \"Sealed-secret (output) file\")\n\tfs.StringVarP(&f.inputFileName, \"secret-file\", \"f\", \"\", \"Secret (input) file\")\n\tfs.BoolVar(&f.dumpCert, \"fetch-cert\", false, \"Write certificate to stdout. Useful for later use with --cert\")\n\tfs.BoolVar(&f.allowEmptyData, \"allow-empty-data\", false, \"Allow empty data in the secret object\")\n\tfs.BoolVar(&f.validateSecret, \"validate\", false, \"Validate that the sealed secret can be decrypted\")\n\tfs.StringVar(&f.mergeInto, \"merge-into\", \"\", \"Merge items from secret into an existing sealed secret file, updating the file in-place instead of writing to stdout.\")\n\tfs.BoolVar(&f.raw, \"raw\", false, \"Encrypt a raw value passed via the --from-* flags instead of the whole secret object\")\n\tfs.StringVar(&f.secretName, \"name\", \"\", \"Name of the sealed secret (required with --raw and default (strict) scope)\")\n\tfs.StringSliceVar(&f.fromFile, \"from-file\", nil, \"(only with --raw) Secret items can be sourced from files. Pro-tip: you can use /dev/stdin to read pipe input. This flag tries to follow the same syntax as in kubectl\")\n\tfs.StringVar(&f.kubeconfig, \"kubeconfig\", \"\", \"Path to a kube config. Only required if out-of-cluster\")\n\n\tfs.Var(&f.sealingScope, \"scope\", \"Set the scope of the sealed secret: strict, namespace-wide, cluster-wide (defaults to strict). Mandatory for --raw, otherwise the 'sealedsecrets.bitnami.com/cluster-wide' and 'sealedsecrets.bitnami.com/namespace-wide' annotations on the input secret can be used to select the scope.\")\n\tfs.BoolVar(&f.reEncrypt, \"rotate\", false, \"\")\n\tfs.BoolVar(&f.reEncrypt, \"re-encrypt\", false, \"Re-encrypt the given sealed secret to use the latest cluster key.\")\n\t_ = fs.MarkDeprecated(\"rotate\", \"please use --re-encrypt instead\")\n\n\tfs.BoolVar(&f.unseal, \"recovery-unseal\", false, \"Decrypt a sealed secrets file obtained from stdin, using the private key passed with --recovery-private-key. Intended to be used in disaster recovery mode.\")\n\tfs.StringSliceVar(&f.privKeys, \"recovery-private-key\", nil, \"Private key filename used by the --recovery-unseal command. Multiple files accepted either via comma separated list or by repetition of the flag. Either PEM encoded private keys or a backup of a json/yaml encoded k8s sealed-secret controller secret (and v1.List) are accepted. \")\n\tfs.BoolVar(&f.help, \"help\", false, \"Print this help message\")\n\n\tfs.SetOutput(os.Stdout)\n}\n\nfunc bindClientFlags(fs *flag.FlagSet, gofs *goflag.FlagSet, overrides *clientcmd.ConfigOverrides) {\n\tflagenv.SetFlagsFromEnv(flagEnvPrefix, gofs)\n\n\tinitUsualKubectlFlags(overrides, fs)\n\n\tpflagenv.SetFlagsFromEnv(flagEnvPrefix, fs)\n\n\t// add klog flags to goflags flagset\n\tklog.InitFlags(nil)\n\t// Standard goflags (glog in particular)\n\tfs.AddGoFlagSet(gofs)\n}\n\nfunc initUsualKubectlFlags(overrides *clientcmd.ConfigOverrides, fs *flag.FlagSet) {\n\tkflags := clientcmd.RecommendedConfigOverrideFlags(\"\")\n\tclientcmd.BindOverrideFlags(overrides, fs, kflags)\n}\n\nfunc runCLI(w io.Writer, cfg *config) (err error) {\n\tflags := cfg.flags\n\n\tif flags.help {\n\t\tfmt.Fprintf(os.Stdout, \"Usage of %s:\\n\", os.Args[0])\n\t\tflag.PrintDefaults()\n\t\treturn nil\n\t}\n\n\tif len(flags.fromFile) != 0 && !flags.raw {\n\t\treturn fmt.Errorf(\"--from-file requires --raw\")\n\t}\n\n\tvar input io.Reader = os.Stdin\n\tif flags.inputFileName != \"\" {\n\t\t// #nosec G304 -- should open user provided file\n\t\tf, err := os.Open(flags.inputFileName)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Could not read file specified with --secret-file\")\n\t\t}\n\t\t// #nosec: G307 -- this deferred close is fine because it is not on a writable file\n\t\tdefer f.Close()\n\n\t\tinput = f\n\t} else if !flags.raw && !flags.dumpCert {\n\t\tif isatty.IsTerminal(os.Stdin.Fd()) {\n\t\t\tfmt.Fprintf(os.Stderr, \"(tty detected: expecting json/yaml k8s resource in stdin)\\n\")\n\t\t}\n\t}\n\n\t// reEncrypt is the only \"in-place\" update subcommand. When the user only provides one file (the input file)\n\t// we'll use the same file for output (see #405).\n\tif flags.reEncrypt && (flags.outputFileName == \"\" && flags.inputFileName != \"\") {\n\t\tflags.outputFileName = flags.inputFileName\n\t}\n\tif flags.outputFileName != \"\" {\n\t\tif ext := filepath.Ext(flags.outputFileName); ext == \".yaml\" || ext == \".yml\" {\n\t\t\tflags.outputFormat = \"yaml\"\n\t\t}\n\n\t\tvar f *renameio.PendingFile\n\t\tf, err = renameio.TempFile(\"\", flags.outputFileName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// only write the output file if the run function exits without errors.\n\t\tdefer func() {\n\t\t\tif err == nil {\n\t\t\t\t_ = f.CloseAtomicallyReplace()\n\t\t\t}\n\t\t}()\n\n\t\tw = f\n\t}\n\n\tif flags.unseal {\n\t\treturn kubeseal.UnsealSealedSecret(w, input, flags.privKeys, flags.outputFormat, scheme.Codecs)\n\t}\n\tif len(flags.privKeys) != 0 && isatty.IsTerminal(os.Stderr.Fd()) {\n\t\tfmt.Fprintf(os.Stderr, \"warning: ignoring --recovery-private-key because unseal command not chosen with --recovery-unseal\\n\")\n\t}\n\n\tif flags.validateSecret {\n\t\treturn kubeseal.ValidateSealedSecret(cfg.ctx, cfg.clientConfig, flags.controllerNs, flags.controllerName, input)\n\t}\n\n\tif flags.reEncrypt {\n\t\treturn kubeseal.ReEncryptSealedSecret(cfg.ctx, cfg.clientConfig, flags.controllerNs, flags.controllerName, flags.outputFormat, input, w, scheme.Codecs)\n\t}\n\n\tf, err := kubeseal.OpenCert(cfg.ctx, cfg.clientConfig, flags.controllerNs, flags.controllerName, flags.certURL)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// #nosec: G307 -- this deferred close is fine because it is not on a writable file\n\tdefer f.Close()\n\n\tif flags.dumpCert {\n\t\t_, err := io.Copy(w, f)\n\t\treturn err\n\t}\n\n\tpubKey, err := kubeseal.ParseKey(f)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif flags.mergeInto != \"\" {\n\t\treturn kubeseal.SealMergingInto(cfg.clientConfig, flags.outputFormat, input, flags.mergeInto, scheme.Codecs, pubKey, flags.sealingScope, flags.allowEmptyData)\n\t}\n\n\tif flags.raw {\n\t\tvar (\n\t\t\tns  string\n\t\t\terr error\n\t\t)\n\t\tif flags.sealingScope < ssv1alpha1.ClusterWideScope {\n\t\t\tns, _, err = cfg.clientConfig.Namespace()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif ns == \"\" {\n\t\t\t\treturn fmt.Errorf(\"must provide the --namespace flag with --raw and --scope %s\", flags.sealingScope.String())\n\t\t\t}\n\n\t\t\tif flags.secretName == \"\" && flags.sealingScope < ssv1alpha1.NamespaceWideScope {\n\t\t\t\treturn fmt.Errorf(\"must provide the --name flag with --raw and --scope %s\", flags.sealingScope.String())\n\t\t\t}\n\t\t}\n\n\t\tvar data []byte\n\t\tif len(flags.fromFile) > 0 {\n\t\t\tif len(flags.fromFile) > 1 {\n\t\t\t\treturn fmt.Errorf(\"must provide only one --from-file when encrypting a single item with --raw\")\n\t\t\t}\n\n\t\t\t_, filename := kubeseal.ParseFromFile(flags.fromFile[0])\n\t\t\t// #nosec G304 -- should open user provided file\n\t\t\tdata, err = os.ReadFile(filename)\n\t\t} else {\n\t\t\tif isatty.IsTerminal(os.Stdin.Fd()) {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"(tty detected: expecting a secret to encrypt in stdin)\\n\")\n\t\t\t}\n\t\t\tdata, err = io.ReadAll(os.Stdin)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn kubeseal.EncryptSecretItem(w, flags.secretName, ns, data, flags.sealingScope, pubKey)\n\t}\n\n\treturn kubeseal.Seal(cfg.clientConfig, flags.outputFormat, input, w, scheme.Codecs, pubKey, flags.sealingScope, flags.allowEmptyData, flags.secretName, \"\")\n}\n\nfunc mainE(w io.Writer, fs *flag.FlagSet, gofs *goflag.FlagSet, args []string) error {\n\tvar flags cliFlags\n\tvar printVersion bool\n\tvar overrides clientcmd.ConfigOverrides\n\tbuildinfo.FallbackVersion(&VERSION, buildinfo.DefaultVersion)\n\n\tfs.BoolVar(&printVersion, \"version\", false, \"Print version information and exit\")\n\tbindFlags(&flags, fs)\n\tbindClientFlags(fs, gofs, &overrides)\n\tif err := fs.Parse(args); err != nil {\n\t\treturn err\n\t}\n\tif err := gofs.Parse([]string{}); err != nil {\n\t\treturn err\n\t}\n\n\tif printVersion {\n\t\tfmt.Fprintf(w, \"kubeseal version: %s\\n\", VERSION)\n\t\treturn nil\n\t}\n\n\tclientConfig := initClient(flags.kubeconfig, &overrides, os.Stdout)\n\tcfg := newConfig(clientConfig, &flags)\n\treturn runCLI(w, cfg)\n}\n\nfunc main() {\n\tif err := mainE(os.Stdout, flag.CommandLine, goflag.CommandLine, os.Args); err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"error: %v\\n\", err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "cmd/kubeseal/main_test.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"encoding/pem\"\n\tgoflag \"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\tssv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/apis/sealedsecrets/v1alpha1\"\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/crypto\"\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/kubeseal\"\n\tflag \"github.com/spf13/pflag\"\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\tclientcmdapi \"k8s.io/client-go/tools/clientcmd/api\"\n\tcertUtil \"k8s.io/client-go/util/cert\"\n\t\"k8s.io/client-go/util/keyutil\"\n)\n\n// mockClientConfig implements clientcmd.ClientConfig for testing\ntype mockClientConfig struct {\n\tnamespace    string\n\tnamespaceSet bool\n}\n\nfunc (m *mockClientConfig) Namespace() (string, bool, error) {\n\treturn m.namespace, m.namespaceSet, nil\n}\n\nfunc (m *mockClientConfig) ClientConfig() (*rest.Config, error) {\n\treturn &rest.Config{}, nil\n}\n\nfunc (m *mockClientConfig) ConfigAccess() clientcmd.ConfigAccess {\n\treturn nil\n}\n\nfunc (m *mockClientConfig) RawConfig() (clientcmdapi.Config, error) {\n\treturn clientcmdapi.Config{}, nil\n}\n\nfunc TestVersion(t *testing.T) {\n\tbuf := bytes.NewBufferString(\"\")\n\ttestVersionFlags := flag.NewFlagSet(\"testVersionFlags\", flag.ExitOnError)\n\tnopFlags := goflag.NewFlagSet(\"nop\", goflag.ExitOnError)\n\terr := mainE(buf, testVersionFlags, nopFlags, []string{\"--version\"})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif got, want := buf.String(), \"kubeseal version: UNKNOWN\\n\"; got != want {\n\t\tt.Errorf(\"got: %q, want: %q\", got, want)\n\t}\n}\n\nfunc testClientConfig() clientcmd.ClientConfig {\n\treturn &mockClientConfig{namespace: \"default\", namespaceSet: false}\n}\n\nfunc testConfig(flags *cliFlags) *config {\n\tclientConfig := testClientConfig()\n\treturn &config{\n\t\tflags:        flags,\n\t\tclientConfig: clientConfig,\n\t\tctx:          context.Background(),\n\t}\n}\n\nfunc TestMainError(t *testing.T) {\n\tbadFileName := filepath.Join(\"this\", \"file\", \"cannot\", \"possibly\", \"exist\", \"can\", \"it?\")\n\tflags := cliFlags{certURL: badFileName}\n\n\terr := runCLI(io.Discard, testConfig(&flags))\n\tif err == nil || !os.IsNotExist(err) {\n\t\tt.Fatalf(\"expecting not exist error, got: %v\", err)\n\t}\n}\n\n// writeTempFile creates a temporary file, writes data into it and closes it.\nfunc writeTempFile(b []byte) (string, error) {\n\ttmp, err := os.CreateTemp(\"\", \"\")\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer tmp.Close()\n\n\tif _, err := tmp.Write(b); err != nil {\n\t\tos.RemoveAll(tmp.Name())\n\t\treturn \"\", err\n\t}\n\n\treturn tmp.Name(), nil\n}\n\nfunc newTestKeyPairSingle(t *testing.T) (*rsa.PublicKey, *rsa.PrivateKey) {\n\tprivKey, _, err := crypto.GeneratePrivateKeyAndCert(2048, time.Hour, \"testcn\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn &privKey.PublicKey, privKey\n}\n\n// testingKeypairFiles returns a path to a PEM encoded certificate and a PEM encoded private key\n// along with a function to be called to cleanup those files.\nfunc testingKeypairFiles(t *testing.T) (string, string, func()) {\n\t_, pk := newTestKeyPairSingle(t)\n\n\tcert, err := crypto.SignKey(rand.Reader, pk, time.Hour, \"testcn\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcertFile, err := writeTempFile(pem.EncodeToMemory(&pem.Block{Type: certUtil.CertificateBlockType, Bytes: cert.Raw}))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpkPEM, err := keyutil.MarshalPrivateKeyToPEM(pk)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tpkFile, err := writeTempFile(pkPEM)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn certFile, pkFile, func() {\n\t\tos.RemoveAll(certFile)\n\t\tos.RemoveAll(pkFile)\n\t}\n}\n\nfunc TestWriteToFile(t *testing.T) {\n\tcertFilename, _, cleanup := testingKeypairFiles(t)\n\tdefer cleanup()\n\n\tin, err := os.CreateTemp(\"\", \"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.RemoveAll(in.Name())\n\tfmt.Fprintf(in, `apiVersion: v1\nkind: Secret\nmetadata:\n  name: foo\n  namespace: bar\ndata:\n  super: c2VjcmV0\n`)\n\tin.Close()\n\n\tout, err := os.CreateTemp(\"\", \"*.yaml\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tout.Close()\n\tdefer os.RemoveAll(out.Name())\n\n\tvar buf bytes.Buffer\n\tflags := cliFlags{\n\t\tinputFileName:  in.Name(),\n\t\toutputFileName: out.Name(),\n\t\tcertURL:        certFilename,\n\t}\n\n\tif err := runCLI(&buf, testConfig(&flags)); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif got, want := buf.Len(), 0; got != want {\n\t\tt.Errorf(\"got: %d, want: %d\", got, want)\n\t}\n\n\tb, err := os.ReadFile(out.Name())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif sub := \"kind: SealedSecret\"; !bytes.Contains(b, []byte(sub)) {\n\t\tt.Errorf(\"expecting to find %q in %q\", sub, b)\n\t}\n}\n\nfunc TestFailToWriteToFile(t *testing.T) {\n\tcertFilename, _, cleanup := testingKeypairFiles(t)\n\tdefer cleanup()\n\n\tin, err := os.CreateTemp(\"\", \"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.RemoveAll(in.Name())\n\tfmt.Fprintf(in, `apiVersion: v1\nkind: BadInput\nmetadata:\n  name: foo\n  namespace: bar\n`)\n\tin.Close()\n\n\tout, err := os.CreateTemp(\"\", \"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// if sealing error happens, the old content of the output file shouldn't be truncated.\n\tconst testOldContent = \"previous content\"\n\n\tfmt.Fprint(out, testOldContent)\n\tout.Close()\n\tdefer os.RemoveAll(out.Name())\n\n\tvar buf bytes.Buffer\n\tflags := cliFlags{\n\t\tinputFileName:  in.Name(),\n\t\toutputFileName: out.Name(),\n\t\tcertURL:        certFilename,\n\t}\n\n\tif err := runCLI(&buf, testConfig(&flags)); err == nil {\n\t\tt.Errorf(\"expecting error\")\n\t}\n\n\tif got, want := buf.Len(), 0; got != want {\n\t\tt.Errorf(\"got: %d, want: %d\", got, want)\n\t}\n\n\tb, err := os.ReadFile(out.Name())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif got, want := string(b), testOldContent; got != want {\n\t\tt.Errorf(\"got: %q, want: %q\", got, want)\n\t}\n}\n\nfunc Test_runCLI(t *testing.T) {\n\ttype args struct {\n\t\tcfg *config\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twantW   string\n\t\twantErr bool\n\t}{\n\t\t// TODO: Add test cases.\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tw := &bytes.Buffer{}\n\t\t\tif err := runCLI(w, tt.args.cfg); (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"runCLI() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif gotW := w.String(); gotW != tt.wantW {\n\t\t\t\tt.Errorf(\"runCLI() = %v, want %v\", gotW, tt.wantW)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype tweakedClientConfig struct {\n\tccfg      kubeseal.ClientConfig\n\tnamespace string\n}\n\nfunc (tcc *tweakedClientConfig) Namespace() (string, bool, error) {\n\treturn tcc.namespace, false, nil\n}\n\nfunc (tcc *tweakedClientConfig) ClientConfig() (*rest.Config, error) {\n\treturn tcc.ccfg.ClientConfig()\n}\n\nfunc trySealTestItem(certFilename, secretNS, secretName, secretValue string, scope ssv1alpha1.SealingScope) (string, error) {\n\tdataFile, err := writeTempFile([]byte(secretValue))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer os.RemoveAll(dataFile)\n\n\tfromFile := []string{dataFile}\n\tvar buf bytes.Buffer\n\tflags := cliFlags{\n\t\tsealingScope: scope,\n\t\tsecretName:   secretName,\n\t\tcertURL:      certFilename,\n\t\traw:          true,\n\t\tfromFile:     fromFile,\n\t}\n\tcfg := testConfig(&flags)\n\tcfg.clientConfig = &tweakedClientConfig{cfg.clientConfig, secretNS}\n\n\tif err := runCLI(&buf, cfg); err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn buf.String(), nil\n}\n\nfunc TestRawSealErrors(t *testing.T) {\n\tcertFilename, _, cleanup := testingKeypairFiles(t)\n\tdefer cleanup()\n\n\tconst (\n\t\tsecretNS    = \"myns\"\n\t\tsecretName  = \"mysecret\"\n\t\tsecretValue = \"supersecret\"\n\t)\n\n\ttestCases := []struct {\n\t\tns      string\n\t\tname    string\n\t\tscope   ssv1alpha1.SealingScope\n\t\tsealErr string\n\t}{\n\t\t{ns: \"\", name: \"\", sealErr: \"must provide the --namespace flag with --raw and --scope strict\"},\n\t\t{ns: secretNS, name: \"\", sealErr: \"must provide the --name flag with --raw and --scope strict\"},\n\t\t{scope: ssv1alpha1.NamespaceWideScope, name: secretName, sealErr: \"must provide the --namespace flag with --raw and --scope namespace-wide\"},\n\t}\n\tfor i, tc := range testCases {\n\t\t// try to encrypt an item and check error response\n\t\tt.Run(fmt.Sprint(i), func(t *testing.T) {\n\t\t\t_, err := trySealTestItem(certFilename, tc.ns, tc.name, secretValue, tc.scope)\n\t\t\tif got, want := fmt.Sprint(err), tc.sealErr; !strings.HasPrefix(got, want) {\n\t\t\t\tt.Fatalf(\"got: %v, want: %v\", err, want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "contrib/prometheus-mixin/.gitignore",
    "content": "manifests/\n"
  },
  {
    "path": "contrib/prometheus-mixin/Makefile",
    "content": "# Prometheus Mixin Makefile\n# Heavily copied from upstream project kubenetes-mixin\n\nPROMETHEUS_IMAGE := prom/prometheus:v2.21.0\n\nJSONNET_FMT := jsonnetfmt\n\nall: fmt prometheus_alerts.yaml prometheus_rules.yaml dashboards_out lint test ## Generate files, lint and test\n\nfmt: ## Format Jsonnet\n\tfind . -name 'vendor' -prune -o -name '*.libsonnet' -print -o -name '*.jsonnet' -print | \\\n\t\txargs -n 1 -- $(JSONNET_FMT) -i\n\nprometheus_alerts.yaml: mixin.libsonnet lib/alerts.jsonnet alerts/*.libsonnet ## Generate Alerts YAML\n\t@mkdir -p manifests\n\tjsonnet -S lib/alerts.jsonnet > manifests/$@\n\nprometheus_rules.yaml: mixin.libsonnet lib/rules.jsonnet rules/*.libsonnet ## Generate Rules YAML\n\t@mkdir -p manifests\n\tjsonnet -S lib/rules.jsonnet > manifests/$@\n\ndashboards_out: mixin.libsonnet lib/dashboards.jsonnet dashboards/*.libsonnet ## Generate Dashboards JSON\n\tjsonnet -J vendor -m manifests lib/dashboards.jsonnet\n\nlint: prometheus_alerts.yaml prometheus_rules.yaml ## Lint and check YAML\n\tfind . -name 'vendor' -prune -o -name '*.libsonnet' -print -o -name '*.jsonnet' -print | \\\n\t\twhile read f; do \\\n\t\t\t$(JSONNET_FMT) \"$$f\" | diff -u \"$$f\" -; \\\n\t\tdone\n\tdocker run \\\n\t\t-v $(PWD)/manifests:/tmp \\\n\t\t--entrypoint '/bin/promtool' \\\n\t\t$(PROMETHEUS_IMAGE) \\\n\t\tcheck rules /tmp/prometheus_rules.yaml; \\\n\tdocker run \\\n\t\t-v $(PWD)/manifests:/tmp \\\n\t\t--entrypoint '/bin/promtool' \\\n\t\t$(PROMETHEUS_IMAGE) \\\n\t\tcheck rules /tmp/prometheus_alerts.yaml\n\nclean: ## Clean up generated files\n\trm -rf manifests/\n\n# TODO: Find out why official prom images segfaults during `test rules` if not root\ntest: prometheus_alerts.yaml prometheus_rules.yaml ## Test generated files\n\tdocker run \\\n\t\t-v $(PWD):/tmp \\\n\t\t--user root \\\n\t\t--entrypoint '/bin/promtool' \\\n\t\t$(PROMETHEUS_IMAGE) \\\n\t\ttest rules /tmp/tests.yaml\n\n.PHONY: help\nhelp:\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n\n"
  },
  {
    "path": "contrib/prometheus-mixin/README.md",
    "content": "# Sealed Secrets Metrics\n\nThe Sealed Secrets Controller running in Kubernetes exposes Prometheus\nmetrics on `*:8081/metrics`.\n\nThese metrics enable operators to observe how it is performing. For example \nhow many `SealedSecret` unseals have been attempted and how many errors may \nhave occured due to RBAC permissions, wrong key, corrupted data, etc.\n\nThese metrics can be scraped by a Prometheus server and viewed in Prometheus,\ndisplayed on a Grafana dashboard and/or trigger alerts to Slack/etc.\n\n## Prometheus Mixin\n\nA Prometheus mixin bundles all of the metric related concerns into a single\npackage for users of the application to consume.\nTypically this includes dashboards, recording rules, alerts and alert logic\ntests.\n\nBy creating a mixin, application maintainers and contributors to the project\ncan enshrine knowledge about operating the application and potential SLO's\nthat users may wish to use. \n\nFor more details about this concept see the [monitoring-mixins](https://github.com/monitoring-mixins/docs)\nproject on GitHub.\n\n## Scraping the metrics manually\n\nAfter installing the Sealed Secrets Controller you can access the metrics via \nKubernetes port-forward to your pod:\n\n```\n$ kubectl port-forward sealed-secrets-controller-6566dc69c6-lqr6x 8081 &\n[1] 293283\n```\n\nThen query the metrics endpoint:\n```\n$ curl localhost:8081/metrics\n\n<snip>\n# HELP sealed_secrets_controller_build_info Build information.\n# TYPE sealed_secrets_controller_build_info gauge\nsealed_secrets_controller_build_info{revision=\"v0.12.1\"} 0\n# HELP sealed_secrets_controller_unseal_errors_total Total number of sealed secret unseal errors by reason\n# TYPE sealed_secrets_controller_unseal_errors_total counter\nsealed_secrets_controller_unseal_errors_total{reason=\"fetch\"} 0\nsealed_secrets_controller_unseal_errors_total{reason=\"status\"} 0\nsealed_secrets_controller_unseal_errors_total{reason=\"unmanaged\"} 0\nsealed_secrets_controller_unseal_errors_total{reason=\"unseal\"} 0\nsealed_secrets_controller_unseal_errors_total{reason=\"update\"} 0\n# HELP sealed_secrets_controller_unseal_requests_total Total number of sealed secret unseal requests\n# TYPE sealed_secrets_controller_unseal_requests_total counter\nsealed_secrets_controller_unseal_requests_total 86\n```\n\n## Scraping metrics with the Prometheus Operator\n\nThe [Prometheus Operator](https://github.com/coreos/prometheus-operator)\nsupports a couple of Kubernetes native scrape target `CustomResourceDefinitions`.\n\nThis project includes a [PodMonitor](../../controller-podmonitor.jsonnet\n) CRD definition in jsonnet. To use this:\n\nCompile jsonnet to yaml:\n```\n$ make controller-podmonitor.yaml \nkubecfg show -V CONTROLLER_IMAGE=docker.io/bitnami/sealed-secrets-controller:latest -V IMAGE_PULL_POLICY=Always -o yaml controller-podmonitor.jsonnet > controller-podmonitor.yaml.tmp\nmv controller-podmonitor.yaml.tmp controller-podmonitor.yaml\n```\n\nSubmit the `PodMonitor` CustomResourceDefinition to Kubernetes API:\n```\n$ kubectl apply -f controller-podmonitor.yaml\n```\n\nThe Prometheus Operator will trigger a reload of Prometheus configuration and\nyou should see the Sealed Secrets Controller in your Prometheus UI under \n`Service Discovery` and `Targets`.\n\n## Grafana dashboard\n\nThe [dashboard](./dashboards/sealed-secrets-controller.json) can be imported\nstandalone into Grafana. You may need to edit the datasource if you have\nconfigured your Prometheus datasource with a different name.\n\n## Using the mixin with kube-prometheus\n\nSee the [kube-prometheus](https://github.com/coreos/kube-prometheus#kube-prometheus)\nproject documentation for instructions on importing mixins.\n\n## Using the mixin as raw YAML files\n\nIf you don't use the jsonnet based `kube-prometheus` project then you will need to\ngenerate the raw yaml files for inclusion in your Prometheus installation.\n\nInstall the `jsonnet` dependencies:\n```\n$ go get github.com/google/go-jsonnet/cmd/jsonnet\n$ go get github.com/google/go-jsonnet/cmd/jsonnetfmt\n```\n\nGenerate yaml:\n```\n$ make\n```\n"
  },
  {
    "path": "contrib/prometheus-mixin/alerts/alerts.libsonnet",
    "content": "// Sealed Secrets Alertmanager Alerts\n\n(import 'sealed-secrets-alerts.libsonnet')\n"
  },
  {
    "path": "contrib/prometheus-mixin/alerts/sealed-secrets-alerts.libsonnet",
    "content": "{\n  prometheusAlerts+:: {\n    groups+: [{\n      name: 'sealed-secrets',\n      rules: [\n        // SealedSecretsErrorRateHigh:\n        // Method: Alert on occurence of errors by looking for a non-zero rate of errors over past 5 minutes\n        // Pros:\n        // - An app deploy is likely broken if a secret can't be updated by Controller.\n        // Caveats:\n        // - Probably better to leave app deploy breakages to the app or CD systems monitoring.\n        // - Potentially noisy. Controller attempts to unseal 5 times, so if it exceeds on the 4th attempt then all is fine but this alert will trigger.\n        // - Usage of an invalid cert.pem with kubeseal will trigger this alert, it would be better to distinguish alerts due to controller or user\n        // - 'for' clause not used because we are unlikely to have a sustained rate of errors unless there is a LOT of secret churn in cluster.\n        // Rob Ewaschuk - My Philosophy on Alerting: https://docs.google.com/document/d/199PqyG3UsyXlwieHaqbGiWVa8eMWi8zzAn0YfcApr8Q/edit\n        {\n          alert: 'SealedSecretsUnsealErrorHigh',\n          expr: |||\n            sum by (reason, namespace) (rate(sealed_secrets_controller_unseal_errors_total{}[5m])) > 0\n          ||| % $._config,\n          // 'for': '5m', // Not used, see caveats above.\n          labels: {\n            severity: 'warning',\n          },\n          annotations: {\n            summary: 'Sealed Secrets Unseal Error High',\n            description: 'High number of errors during unsealing Sealed Secrets in {{ $labels.namespace }} namespace.',\n            runbook_url: 'https://github.com/bitnami-labs/sealed-secrets',\n          },\n        },\n      ],\n    }],\n  },\n}\n"
  },
  {
    "path": "contrib/prometheus-mixin/config.libsonnet",
    "content": "// Sealed Secrets Prometheus Mixin Config\n{\n  _config+:: {},\n}\n"
  },
  {
    "path": "contrib/prometheus-mixin/dashboards/dashboards.libsonnet",
    "content": "// Sealed Secrets Grafana Dashboards\n\n{\n  grafanaDashboards+:: {\n    'sealed-secrets-controller.json': (import 'sealed-secrets-controller.json'),\n  },\n}\n"
  },
  {
    "path": "contrib/prometheus-mixin/dashboards/sealed-secrets-controller.json",
    "content": "{\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": \"-- Grafana --\",\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"description\": \"Sealed Secrets Controller\",\n  \"editable\": true,\n  \"gnetId\": null,\n  \"graphTooltip\": 0,\n  \"id\": 3,\n  \"iteration\": 1585599163503,\n  \"links\": [\n    {\n      \"icon\": \"external link\",\n      \"tags\": [],\n      \"title\": \"GitHub\",\n      \"tooltip\": \"View Project on GitHub\",\n      \"type\": \"link\",\n      \"url\": \"https://github.com/bitnami-labs/sealed-secrets\"\n    }\n  ],\n  \"panels\": [\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"$datasource\",\n      \"description\": \"Rate of requests to unseal a SealedSecret.\\n\\nThis can include non-obvious operations such as deleting a SealedSecret.\",\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 12,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"hiddenSeries\": false,\n      \"id\": 2,\n      \"legend\": {\n        \"avg\": true,\n        \"current\": false,\n        \"max\": true,\n        \"min\": true,\n        \"show\": true,\n        \"total\": false,\n        \"values\": true\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null\",\n      \"options\": {\n        \"dataLinks\": []\n      },\n      \"percentage\": false,\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(rate(sealed_secrets_controller_unseal_requests_total{}[1m]))\",\n          \"format\": \"time_series\",\n          \"instant\": false,\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"rps\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Unseal Request Rate/s\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    },\n    {\n      \"aliasColors\": {},\n      \"bars\": false,\n      \"dashLength\": 10,\n      \"dashes\": false,\n      \"datasource\": \"$datasource\",\n      \"description\": \"Rate of errors when unsealing a SealedSecret. \\n\\nReason for error included as label value, eg:\\n- unseal = cryptography issue (key/namespace) or RBAC\\n- unmanaged = destination Secret wasn't created by SealedSecrets\\n- update = potentially RBAC\\n- status = potentially RBAC\\n- fetch = potentially RBAC\\n\",\n      \"fill\": 1,\n      \"fillGradient\": 0,\n      \"gridPos\": {\n        \"h\": 9,\n        \"w\": 12,\n        \"x\": 12,\n        \"y\": 0\n      },\n      \"hiddenSeries\": false,\n      \"id\": 3,\n      \"legend\": {\n        \"avg\": false,\n        \"current\": false,\n        \"hideEmpty\": false,\n        \"hideZero\": false,\n        \"max\": false,\n        \"min\": false,\n        \"show\": true,\n        \"total\": false,\n        \"values\": false\n      },\n      \"lines\": true,\n      \"linewidth\": 1,\n      \"links\": [],\n      \"nullPointMode\": \"null as zero\",\n      \"options\": {\n        \"dataLinks\": []\n      },\n      \"percentage\": false,\n      \"pointradius\": 2,\n      \"points\": false,\n      \"renderer\": \"flot\",\n      \"seriesOverrides\": [],\n      \"spaceLength\": 10,\n      \"stack\": false,\n      \"steppedLine\": false,\n      \"targets\": [\n        {\n          \"expr\": \"sum(rate(sealed_secrets_controller_unseal_errors_total{pod=~\\\"$pod\\\"}[1m])) by (reason)\",\n          \"format\": \"time_series\",\n          \"intervalFactor\": 1,\n          \"legendFormat\": \"{{ reason }}\",\n          \"refId\": \"A\"\n        }\n      ],\n      \"thresholds\": [],\n      \"timeFrom\": null,\n      \"timeRegions\": [],\n      \"timeShift\": null,\n      \"title\": \"Unseal Error Rate/s\",\n      \"tooltip\": {\n        \"shared\": true,\n        \"sort\": 0,\n        \"value_type\": \"individual\"\n      },\n      \"type\": \"graph\",\n      \"xaxis\": {\n        \"buckets\": null,\n        \"mode\": \"time\",\n        \"name\": null,\n        \"show\": true,\n        \"values\": []\n      },\n      \"yaxes\": [\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        },\n        {\n          \"format\": \"short\",\n          \"label\": null,\n          \"logBase\": 1,\n          \"max\": null,\n          \"min\": null,\n          \"show\": true\n        }\n      ],\n      \"yaxis\": {\n        \"align\": false,\n        \"alignLevel\": null\n      }\n    }\n  ],\n  \"refresh\": false,\n  \"schemaVersion\": 22,\n  \"style\": \"dark\",\n  \"tags\": [],\n  \"templating\": {\n    \"list\": [\n      {\n        \"current\": {\n          \"text\": \"prometheus\",\n          \"value\": \"prometheus\"\n        },\n        \"hide\": 0,\n        \"includeAll\": false,\n        \"label\": null,\n        \"multi\": false,\n        \"name\": \"datasource\",\n        \"options\": [],\n        \"query\": \"prometheus\",\n        \"refresh\": 1,\n        \"regex\": \"\",\n        \"skipUrlSync\": false,\n        \"type\": \"datasource\"\n      },\n      {\n        \"allValue\": null,\n        \"current\": {\n          \"selected\": false,\n          \"text\": \"All\",\n          \"value\": \"$__all\"\n        },\n        \"datasource\": \"$datasource\",\n        \"definition\": \"label_values(kube_pod_info, pod)\",\n        \"hide\": 0,\n        \"includeAll\": true,\n        \"label\": null,\n        \"multi\": false,\n        \"name\": \"pod\",\n        \"options\": [],\n        \"query\": \"label_values(kube_pod_info, pod)\",\n        \"refresh\": 1,\n        \"regex\": \"/^sealed-secrets-controller.*$/\",\n        \"skipUrlSync\": false,\n        \"sort\": 0,\n        \"tagValuesQuery\": \"\",\n        \"tags\": [],\n        \"tagsQuery\": \"\",\n        \"type\": \"query\",\n        \"useTags\": false\n      }\n    ]\n  },\n  \"time\": {\n    \"from\": \"now-1h\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {\n    \"refresh_intervals\": [\n      \"5s\",\n      \"10s\",\n      \"30s\",\n      \"1m\",\n      \"5m\",\n      \"15m\",\n      \"30m\",\n      \"1h\",\n      \"2h\",\n      \"1d\"\n    ],\n    \"time_options\": [\n      \"5m\",\n      \"15m\",\n      \"1h\",\n      \"6h\",\n      \"12h\",\n      \"24h\",\n      \"2d\",\n      \"7d\",\n      \"30d\"\n    ]\n  },\n  \"timezone\": \"\",\n  \"title\": \"Sealed Secrets Controller\",\n  \"uid\": \"UuEtZCVWz\",\n  \"version\": 2\n}\n"
  },
  {
    "path": "contrib/prometheus-mixin/lib/alerts.jsonnet",
    "content": "std.manifestYamlDoc((import '../mixin.libsonnet').prometheusAlerts)\n"
  },
  {
    "path": "contrib/prometheus-mixin/lib/dashboards.jsonnet",
    "content": "local dashboards = (import '../mixin.libsonnet').grafanaDashboards;\n\n{\n  [name]: dashboards[name]\n  for name in std.objectFields(dashboards)\n}\n"
  },
  {
    "path": "contrib/prometheus-mixin/lib/rules.jsonnet",
    "content": "std.manifestYamlDoc((import '../mixin.libsonnet').prometheusRules)\n"
  },
  {
    "path": "contrib/prometheus-mixin/mixin.libsonnet",
    "content": "// Prometheus Mixin\n// Follows the kubernetes-mixin project pattern here: https://github.com/kubernetes-monitoring/kubernetes-mixin\n// Mixin design doc: https://docs.google.com/document/d/1A9xvzwqnFVSOZ5fD3blKODXfsat5fg6ZhnKu9LK3lB4/edit\n\n// This file will be imported during build for all Promethei\n\n(import 'config.libsonnet') +\n(import 'alerts/alerts.libsonnet') +\n(import 'dashboards/dashboards.libsonnet') +\n(import 'rules/rules.libsonnet')\n"
  },
  {
    "path": "contrib/prometheus-mixin/rules/rules.libsonnet",
    "content": "// Sealed Secrets Prometheus Recording Rules\n{\n  prometheusRules+:: {\n    groups+: [\n      // import ('sealed-secrets-rules.libsonnet')\n    ],\n  },\n}\n"
  },
  {
    "path": "contrib/prometheus-mixin/tests.yaml",
    "content": "---\nrule_files:\n- /tmp/manifests/prometheus_alerts.yaml\n- /tmp/manifests/prometheus_rules.yaml\n\nevaluation_interval: 1m\n\ntests:\n- interval: 1m\n  input_series:\n  - series: 'sealed_secrets_controller_unseal_errors_total{reason=\"update\",namespace=\"test\"}'\n    values: '0+0x5 1+1x5'\n  - series: 'sealed_secrets_controller_unseal_errors_total{reason=\"unseal\",namespace=\"test\"}'\n    values: '0+0x10'\n  alert_rule_test:\n  - eval_time: 5m\n    alertname: SealedSecretsUnsealErrorHigh\n  - eval_time: 10m\n    alertname: SealedSecretsUnsealErrorHigh\n    exp_alerts:\n    - exp_labels:\n        severity: warning\n        namespace: test\n        reason: update\n      exp_annotations:\n        summary: 'Sealed Secrets Unseal Error High'\n        description: 'High number of errors during unsealing Sealed Secrets in test namespace.'\n        runbook_url: 'https://github.com/bitnami-labs/sealed-secrets'\n"
  },
  {
    "path": "controller-norbac.jsonnet",
    "content": "// Minimal required deployment for a functional controller.\n\nlocal kubecfg = import 'kubecfg.libsonnet';\n\nlocal namespace = 'kube-system';\n\n{\n  kube:: (import 'vendor_jsonnet/kube-libsonnet/kube.libsonnet'),\n  local kube = self.kube + import 'kube-fixes.libsonnet',\n\n  controllerImage:: std.extVar('CONTROLLER_IMAGE'),\n  imagePullPolicy:: local ext = std.extVar('IMAGE_PULL_POLICY'); if ext == '' then\n    if std.endsWith($.controllerImage, ':latest') then 'Always' else 'IfNotPresent'\n  else ext,\n\n  crd: kube.CustomResourceDefinition('bitnami.com', 'v1alpha1', 'SealedSecret') {\n    spec+: {\n      versions_+: {\n        v1alpha1+: {\n          served: true,\n          storage: true,\n          subresources: {\n            status: {},\n          },\n          schema: kubecfg.parseYaml(importstr 'schema-v1alpha1.yaml')[0],\n        },\n      },\n    },\n  },\n\n  namespace:: { metadata+: { namespace: namespace } },\n\n  service: kube.Service('sealed-secrets-controller') + $.namespace {\n    target_pod: $.controller.spec.template,\n  },\n\n  service_metrics: kube.Service('sealed-secrets-controller-metrics') + $.namespace {\n    local service = self,\n    target_pod: $.controller.spec.template,\n    spec: {\n      selector: service.target_pod.metadata.labels,\n      ports: [\n        {\n          port: 8081,\n          targetPort: 8081,\n        },\n      ],\n      type: \"ClusterIP\",\n    },\n  },\n\n  controller: kube.Deployment('sealed-secrets-controller') + $.namespace {\n    spec+: {\n      template+: {\n        spec+: {\n          securityContext+: {\n            fsGroup: 65534,\n            runAsNonRoot: true,\n            runAsUser: 1001,\n            seccompProfile+: {\n              type: 'RuntimeDefault',\n            }\n          },\n          containers_+: {\n            controller: kube.Container('sealed-secrets-controller') {\n              image: $.controllerImage,\n              imagePullPolicy: $.imagePullPolicy,\n              command: ['controller'],\n              readinessProbe: {\n                httpGet: { path: '/healthz', port: 'http' },\n              },\n              livenessProbe: self.readinessProbe,\n              ports_+: {\n                http: { containerPort: 8080 },\n                metrics: { containerPort: 8081 },\n              },\n              securityContext+: {\n                allowPrivilegeEscalation: false,\n                capabilities+: {\n                  drop: [ 'ALL' ],\n                },\n                readOnlyRootFilesystem: true,\n              },\n              volumeMounts_+: {\n                tmp: {\n                  mountPath: '/tmp',\n                },\n              },\n            },\n          },\n          volumes_+: {\n            tmp: {\n              emptyDir: {},\n            },\n          },\n        },\n      },\n    },\n  },\n}\n"
  },
  {
    "path": "controller-podmonitor.jsonnet",
    "content": "// Prometheus Pod Monitor manifest\n// ref: https://github.com/prometheus-operator/prometheus-operator#customresourcedefinitions\n\nlocal controller = import 'controller.jsonnet';\n\ncontroller {\n  podMonitor: {\n    apiVersion: 'monitoring.coreos.com/v1',\n    kind: 'PodMonitor',\n    metadata: {\n      name: 'sealed-secrets-controller',\n      namespace: $.namespace.metadata.namespace,\n      labels: {\n        name: 'sealed-secrets-controller',\n      },\n    },\n    spec: {\n      jobLabel: 'name',\n      selector: {\n        matchLabels: {\n          name: 'sealed-secrets-controller',\n        },\n      },\n      namespaceSelector: {\n        matchNames: [\n          $.namespace.metadata.namespace,\n        ],\n      },\n      podMetricsEndpoints: [\n        {\n          honorLabels: true,  // prefer controller metric namespace\n          port: 'http',\n          interval: '30s',\n        },\n      ],\n      sampleLimit: 1000,\n    },\n  },\n}\n"
  },
  {
    "path": "controller.jsonnet",
    "content": "// This is the recommended cluster deployment of sealed-secrets.\n// See controller-norbac.jsonnet for the bare minimum functionality.\n\nlocal controller = import 'controller-norbac.jsonnet';\n\ncontroller {\n  local kube = self.kube,\n\n  account: kube.ServiceAccount('sealed-secrets-controller') + $.namespace,\n\n  unsealerRole: kube.ClusterRole('secrets-unsealer') {\n    rules: [\n      {\n        apiGroups: ['bitnami.com'],\n        resources: ['sealedsecrets'],\n        verbs: ['get', 'list', 'watch'],\n      },\n      {\n        apiGroups: ['bitnami.com'],\n        resources: ['sealedsecrets/status'],\n        verbs: ['update'],\n      },\n      {\n        apiGroups: [''],\n        resources: ['secrets'],\n        verbs: ['get', 'list', 'create', 'update', 'delete', 'watch'],\n      },\n      {\n        apiGroups: [''],\n        resources: ['events'],\n        verbs: ['create', 'patch'],\n      },\n      {\n        apiGroups: [''],\n        resources: ['namespaces'],\n        verbs: ['get'],\n      },\n    ],\n  },\n\n  unsealKeyRole: kube.Role('sealed-secrets-key-admin') + $.namespace {\n    rules: [\n      {\n        apiGroups: [''],\n        resources: ['secrets'],\n        // Can't limit create by resource name as keys are produced on the fly\n        verbs: ['create', 'list'],\n      },\n    ],\n  },\n\n  serviceProxierRole: kube.Role('sealed-secrets-service-proxier') + $.namespace {\n    rules: [\n      {\n        apiGroups: [\n          '',\n        ],\n        resources: [\n          'services',\n        ],\n        resourceNames: [\n          'sealed-secrets-controller',\n        ],\n        // kubeseal dynamically obtains the service port name so later on\n        // can access the service using a proxy\n        verbs: [\n          'get',\n        ],\n      },\n      {\n        apiGroups: [\n          '',\n        ],\n        resources: [\n          'services/proxy',\n        ],\n        resourceNames: [\n          'http:sealed-secrets-controller:',  // kubeseal uses net.JoinSchemeNamePort when crafting proxy subresource URLs\n          'http:sealed-secrets-controller:http',\n          'sealed-secrets-controller',  // but often services are referred by name only, let's not make it unnecessarily cryptic\n        ],\n        verbs: [\n          'create',  // rotate and validate endpoints expect POST, see https://kubernetes.io/docs/reference/access-authn-authz/authorization/#determine-the-request-verb\n          'get',\n        ],\n      },\n    ],\n  },\n\n  unsealerBinding: kube.ClusterRoleBinding('sealed-secrets-controller') {\n    roleRef_: $.unsealerRole,\n    subjects_+: [$.account],\n  },\n\n  unsealKeyBinding: kube.RoleBinding('sealed-secrets-controller') + $.namespace {\n    roleRef_: $.unsealKeyRole,\n    subjects_+: [$.account],\n  },\n\n  serviceProxierBinding: kube.RoleBinding('sealed-secrets-service-proxier') + $.namespace {\n    roleRef_: $.serviceProxierRole,\n    // kube.libsonnet assumes object here have a namespace, but system groups don't\n    // thus are not supposed to use the magic \"_\" here.\n    subjects+: [kube.Group('system:authenticated')],\n  },\n\n  controller+: {\n    spec+: {\n      template+: {\n        spec+: {\n          serviceAccountName: $.account.metadata.name,\n        },\n      },\n    },\n  },\n}\n"
  },
  {
    "path": "docker/controller.Dockerfile",
    "content": "FROM gcr.io/distroless/static@sha256:47b2d72ff90843eb8a768b5c2f89b40741843b639d065b9b937b07cd59b479c6\nLABEL maintainer \"Sealed Secrets <sealed-secrets.pdl@broadcom.com>\"\n\nUSER 1001\n\nARG TARGETARCH\nCOPY dist/controller_linux_${TARGETARCH}*/controller /usr/local/bin/\n\nEXPOSE 8080 8081\n\nENTRYPOINT [\"controller\"]\n"
  },
  {
    "path": "docker/kubeseal.Dockerfile",
    "content": "FROM gcr.io/distroless/static@sha256:47b2d72ff90843eb8a768b5c2f89b40741843b639d065b9b937b07cd59b479c6\nLABEL maintainer \"Sealed Secrets <sealed-secrets.pdl@broadcom.com>\"\n\nUSER 1001\n\nARG TARGETARCH\nCOPY dist/kubeseal_linux_${TARGETARCH}*/kubeseal /usr/local/bin/\n\nENTRYPOINT [\"kubeseal\"]\n"
  },
  {
    "path": "docs/GKE.md",
    "content": "<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->\n**Table of Contents**  *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n- [GKE](#gke)\n  - [Install](#install)\n  - [Private GKE clusters](#private-gke-clusters)\n    - [Offline sealing](#offline-sealing)\n    - [Control Plane to Node firewall](#control-plane-to-node-firewall)\n- [RBAC and GKE Warden Restrictions](#rbac-and-gke-warden-restrictions)\n- [Workarounds](#workarounds)\n  - [Option 1: Disable the service-proxier (Simplest)](#option-1-disable-the-service-proxier-simplest)\n  - [Option 2: Use Google Groups for RBAC (Recommended)](#option-2-use-google-groups-for-rbac-recommended)\n\n<!-- END doctoc generated TOC please keep comment here to allow auto update -->\n\n# GKE\n\n## Install\n\nIf installing on a GKE cluster you don't have admin rights for, a ClusterRoleBinding may be needed to successfully deploy the controller in the final command.  Replace <your-email> with a valid email, and then deploy the cluster role binding:\n\n```bash\nUSER_EMAIL=<your-email>\nkubectl create clusterrolebinding $USER-cluster-admin-binding --clusterrole=cluster-admin --user=$USER_EMAIL\n```\n\n## Private GKE clusters\n\nIf you are using a **private GKE cluster**, `kubeseal` won't be able to fetch the public key from the controller\nbecause there is firewall that prevents the control plane to talk directly to the nodes.\n\nThere are currently two workarounds:\n\n### Offline sealing\n\nIf you have the public key for your controller, you can seal secrets without talking to the controller.\nNormally `kubeseal --fetch-cert` can be used to obtain the certificate for later use, but in this case the firewall prevents us from doing it.\n\nThe controller outputs the certificate to the logs so you can copy paste it from there.\n\nOnce you have the cert this is how you seal secrets:\n\n```bash\nkubeseal --cert=cert.pem <secret.yaml\n```\n\n### Control Plane to Node firewall\n\nYou are required to create a Control Plane to Node firewall rule to allow GKE to communicate to the kubeseal container endpoint port tcp/8080.\n\n```bash\nCLUSTER_NAME=foo-cluster\ngcloud config set compute/zone your-zone-or-region\n```\n\nGet the `CP_IPV4_CIDR`.\n\n```bash\nCP_IPV4_CIDR=$(gcloud container clusters describe $CLUSTER_NAME \\\n  | grep \"masterIpv4CidrBlock: \" \\\n  | awk '{print $2}')\n```\n\nGet the `NETWORK`.\n\n```bash\nNETWORK=$(gcloud container clusters describe $CLUSTER_NAME \\\n  | grep \"^network: \" \\\n  | awk '{print $2}')\n```\n\nGet the `NETWORK_TARGET_TAG`.\n\n```bash\nNETWORK_TARGET_TAG=$(gcloud compute firewall-rules list \\\n  --filter network=$NETWORK --format json \\\n  | jq \".[] | select(.name | contains(\\\"$CLUSTER_NAME\\\"))\" \\\n  | jq -r '.targetTags[0]' | head -1)\n```\n\nCheck the values.\n\n```bash\necho $CP_IPV4_CIDR $NETWORK $NETWORK_TARGET_TAG\n\n# example output\n10.0.0.0/28 foo-network gke-foo-cluster-c1ecba83-node\n```\n\nCreate the firewall rule.\n\n```bash\ngcloud compute firewall-rules create gke-to-kubeseal-8080 \\\n  --network \"$NETWORK\" \\\n  --allow \"tcp:8080\" \\\n  --source-ranges \"$CP_IPV4_CIDR\" \\\n  --target-tags \"$NETWORK_TARGET_TAG\" \\\n  --priority 1000\n```\n\nCreate the firewall rule to see the metrics\n\n```bash\ngcloud compute firewall-rules create gke-to-metrics-8081 \\\n  --network \"$NETWORK\" \\\n  --allow \"tcp:8081\" \\\n  --source-ranges \"$CP_IPV4_CIDR\" \\\n  --target-tags \"$NETWORK_TARGET_TAG\" \\\n  --priority 1000\n```\n# RBAC and GKE Warden Restrictions\n\nOn GKE clusters running version `1.32.2-gke.1182003` or later, the **GKE\nWarden admission webhook** strictly forbids binding any `Role` or\n`ClusterRole` to the `system:authenticated` group.\n\nBy default, the `sealed-secrets` Helm chart binds the `service-proxier`\nrole to this group to allow `kubeseal` to communicate with the\ncontroller and fetch the public key.\n\nOn modern GKE versions, this default configuration will cause the\ninstallation to fail with the following error:\n\n``` text\nadmission webhook \"warden-validating.common-webhooks.networking.gke.io\" denied the request:\nGKE Warden rejected the request because it violates one or more constraints.\nViolations details:\n{\"[denied by rbac-binding-limitation]\":[\"Binding any Role or ClusterRole to Group \\\"system:authenticated\\\" is forbidden.\"]}\n```\n\n------------------------------------------------------------------------\n\n# Workarounds\n\nTo successfully deploy on GKE, you must override the default\n`serviceProxier` settings in your `values.yaml`.\n\n------------------------------------------------------------------------\n\n## Option 1: Disable the service-proxier (Simplest)\n\nIf you do not need the `kubeseal --fetch-cert` functionality through the\nproxier, you can disable its creation entirely:\n\n``` yaml\nrbac:\n  serviceProxier:\n    create: false\n```\n\n------------------------------------------------------------------------\n\n## Option 2: Use Google Groups for RBAC (Recommended)\n\nFor a more secure setup, bind the proxier role to a specific restricted\nGoogle Group instead of the broad `system:authenticated` group.\n\nThis requires:\n\n1.  Setting up Google Groups for RBAC in your Google Cloud organization.\n2.  Creating an \"anchor\" group named\n    `gke-security-groups@yourdomain.com`.\n3.  Updating your Helm values to point to your specific subgroup:\n\n``` yaml\nrbac:\n  serviceProxier:\n    subjects:\n      - apiGroup: rbac.authorization.k8s.io\n        kind: Group\n        name: \"your-restricted-group@yourdomain.com\"\n```\n"
  },
  {
    "path": "docs/bring-your-own-certificates.md",
    "content": "# Bring your own certificates\n\nThe controller generates its own certificates when is deployed for the first time, it also manages the renewal for you.\nBut you can also bring your own certificates so the controller can consume them as well.\n\nThe controller consumes certificates contained in any secret labeled with `sealedsecrets.bitnami.com/sealed-secrets-key=active`,\nthe secret has to live in the same namespace as the controller. There can be multiple such secrets.\n\nBelow you can find all the steps needed to create and consume your own certificates.\n\n## Set your vars\n\n```bash\nexport PRIVATEKEY=\"mytls.key\"\nexport PUBLICKEY=\"mytls.crt\"\nexport NAMESPACE=\"sealed-secrets\"\nexport SECRETNAME=\"mycustomkeys\"\n```\n\n## Generate a new RSA key pair (certificates)\n* Note to change `-days` option to set certificate expiry date; default is 1 year\n```bash\nopenssl req -x509 -days 365 -nodes -newkey rsa:4096 -keyout \"$PRIVATEKEY\" -out \"$PUBLICKEY\" -subj \"/CN=sealed-secret/O=sealed-secret\"\n```\n\n## Create a tls k8s secret, using your recently created RSA key pair\n\n```bash\nkubectl -n \"$NAMESPACE\" create secret tls \"$SECRETNAME\" --cert=\"$PUBLICKEY\" --key=\"$PRIVATEKEY\"\nkubectl -n \"$NAMESPACE\" label secret \"$SECRETNAME\" sealedsecrets.bitnami.com/sealed-secrets-key=active\n```\n\n## Deleting the controller Pod is needed to pick the new keys\n\n```bash\nkubectl -n  \"$NAMESPACE\" delete pod -l name=sealed-secrets-controller\n```\n\n## See the new certificates (private keys) in the controller logs\n\n```bash\nkubectl -n \"$NAMESPACE\" logs -l name=sealed-secrets-controller\n\ncontroller version: v0.12.1+dirty\n2020/06/09 14:30:45 Starting sealed-secrets controller version: v0.12.1+dirty\n2020/06/09 14:30:45 Searching for existing private keys\n2020/06/09 14:30:45 registered private key secretname=sealed-secrets-key5rxd9\n2020/06/09 14:30:45 registered private key secretname=mycustomkeys\n2020/06/09 14:30:45 HTTP server serving on :8080\n```\n\n## Try your own certificates\n\nNow you can try to seal a secret with your own certificate, instead of using the controller provided ones.\n\n### Used your recently created public key to \"seal\" your secret\n\nUse your own certificate (key) by using the `--cert` flag:\n\n```bash\nkubeseal --cert \"./${PUBLICKEY}\" --scope cluster-wide < mysecret.yaml | kubectl apply -f-\n```\n\n### We can see the secret has been unsealed succesfully\n\n```bash\nkubectl -n \"$NAMESPACE\" logs -l name=sealed-secrets-controller\n\ncontroller version: v0.12.1+dirty\n2020/06/09 14:30:45 Starting sealed-secrets controller version: v0.12.1+dirty\n2020/06/09 14:30:45 Searching for existing private keys\n2020/06/09 14:30:45 ----- sealed-secrets-key5rxd9\n2020/06/09 14:30:45 ----- mycustomkeys\n2020/06/09 14:30:45 HTTP server serving on :8080\n2020/06/09 14:37:55 Updating test-namespace/mysecret\n2020/06/09 14:37:55 Event(v1.ObjectReference{Kind:\"SealedSecret\", Namespace:\"test-namespace\", Name:\"mysecret\", UID:\"f3a6c537-d254-4c06-b08f-ab9548f28f5b\", APIVersion:\"bitnami.com/v1alpha1\", ResourceVersion:\"20469957\", FieldPath:\"\"}): type: 'Normal' reason: 'Unsealed' SealedSecret unsealed successfully\n```\n\n**NOTE:**\n\n`$PRIVATEKEY` is your private key, which is used by the controller to unseal your secret.\nDon't share it with anyone you don't trust, and save it in a safe place!!\n"
  },
  {
    "path": "docs/developer/README.md",
    "content": "# Developer Guide\n\n**Table of Contents**\n\n<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->\n\n- [Prerequisites](#prerequisites)\n- [The Sealed Secrets Components](#the-sealed-secrets-components)\n  - [Controller](#controller)\n  - [Kubeseal](#kubeseal)\n- [git-hooks](#git-hooks)\n\n<!-- END doctoc generated TOC please keep comment here to allow auto update -->\n\n## Prerequisites\n\nTo be able to develop on this project, you need to have the following tools installed:\n\n- [Git](https://git-scm.com/)\n- [Make](https://www.gnu.org/software/make/)\n- [Go programming language](https://golang.org/dl/)\n- [Docker CE](https://www.docker.com/community-edition)\n- [Kubernetes cluster (v1.16+)](https://kubernetes.io/docs/setup/). [Minikube](https://github.com/kubernetes/minikube) is recommended.\n- [Kubecfg](https://github.com/bitnami/kubecfg)\n- [Ginkgo](https://onsi.github.io/ginkgo/)\n- [git-hooks](https://github.com/git-hooks/git-hooks)\n- [doctoc](https://github.com/thlorenz/doctoc)\n\n## The Sealed Secrets Components\n\nSealed Secrets is composed of three parts:\n\n- A [custom resource](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources) named `SealedSecret`\n- A cluster-side controller / operator that manages the `SealedSecret` objects\n- A client-side utility: kubeseal\n\n### Controller\n\nThe controller is in charge of keeping the current state of `SealedSecret` objects in sync with the declared desired state.\n\nPlease refer to the [Sealed Secrets Controller](controller.md) for the developer setup.\n\n### Kubeseal\n\nThe `kubeseal` utility uses asymmetric crypto to encrypt secrets that only the controller can decrypt.\n\nPlease refer to the [Kubeseal Developer Guide](kubeseal.md) for the developer setup.\n\n## git-hooks\n\nTo avoid easily detectable issues and prevent them from reaching main, some validations have been implemented via [git hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks). To have those hooks committed in the repository you need to install a third party tool `git-hooks` (check [prerequisites](#prerequisites)), because the hooks provided by Git are stored in the `.git` directory that is not included as part of the repositories.\n\nCurrently, there's a single hook at pre-commit level. This hook ensures the Table of Contents (TOC) is updated using `doctoc` (check [prerequisites](#prerequisites)) in every `.md` and `.txt` file that uses this tool.\n\nConfigure git-hooks for this specific repository by running `git hooks install`. You can check with the following command if everything was configured properly:\n\n```console\n$ git hooks list\nGit hooks ARE installed in this repository.\nproject hooks\n  pre-commit\n    - doc-toc\n\nContrib hooks\n```\n"
  },
  {
    "path": "docs/developer/controller.md",
    "content": "# Controller Developer Guide\n\nThe controller is in charge of keeping the current state of `SealedSecret` objects in sync with the declared desired state.\n\nThe controller exposes an API defined using the Swagger or OpenAPI v3 specification. You can download the definition from the link below:\n\n- [swagger.yml](swagger.yml)\n\n<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->\n**Table of Contents**  *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n- [Download the controller source code](#download-the-controller-source-code)\n  - [Setup a kubernetes cluster to run the tests](#setup-a-kubernetes-cluster-to-run-the-tests)\n  - [Run all controller tests with a single command](#run-all-controller-tests-with-a-single-command)\n  - [Run tests step by step](#run-tests-step-by-step)\n    - [Building the `controller` binary](#building-the-controller-binary)\n    - [Running unit tests](#running-unit-tests)\n    - [Push the controller image](#push-the-controller-image)\n    - [Building & applying the controller manifests](#building--applying-the-controller-manifests)\n    - [Running integration tests](#running-integration-tests)\n\n<!-- END doctoc generated TOC please keep comment here to allow auto update -->\n\n## Download the controller source code\n\n```bash\ngit clone https://github.com/bitnami-labs/sealed-secrets.git $SEALED_SECRETS_DIR\n```\n\nThe controller sources are located under `cmd/controller/` and use packages from the `pkg` directory.\n\n\n### Setup a kubernetes cluster to run the tests\n\nYou need a kubernetes cluster to run the integration tests.\n\nFor instance:\n\nWhen using a local minikube, configure your local environment to re-use the local Docker daemon:\n\n```bash\nminikube start\neval $(minikube docker-env)\n```\n\nIf you use `kind` instead, you can setup a local companion image registry and allow kind to access it.\n\nSample to run a registry locally:\n```bash\nexport LOCAL_REGISTRY_PORT='5000'\nexport LOCAL_REGISTRY_NAME='kind-registry'\ndocker run --rm -d -p \"127.0.0.1:${LOCAL_REGISTRY_PORT}:5000\" --name \"${LOCAL_REGISTRY_NAME}\" registry:2\n```\n\nThen to have launch `kind` with access to that registry:\n```bash\ncat <<EOF | kind create cluster --name \"${CLUSTER_NAME}\" --config=-\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\ncontainerdConfigPatches:\n- |-\n  [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"localhost:${LOCAL_REGISTRY_PORT}\"]\n    endpoint = [\"http://${LOCAL_REGISTRY_NAME}:5000\"]\nEOF\ndocker network connect \"kind\" \"${LOCAL_REGISTRY_NAME}\"\n```\n\n### Run all controller tests with a single command\n\n```bash\nmake K8S_CONTEXT=mytestk8s-context OS=linux ARCH=amd64 controller-tests\n```\n\nNote that:\n- `K8S_CONTEXT` must be set to the name of your `kubectl` context pointing to the expected text cluster.\n- `OS` & `ARCH` must match the Operating System and Architecture of your test cluster.\n\nOptionally, you can customize the `REGISTRY` as well. In fact you will need that for a kind setup with a local registry:\n\n```bash\nmake K8S_CONTEXT=kind REGISTRY=localhost:5000 OS=linux ARCH=amd64 controller-tests\n```\n\nFor minikube just skip the `REGISTRY` setting:\n```bash\nmake K8S_CONTEXT=minikube OS=linux ARCH=amd64 controller-tests\n```\n\n### Run tests step by step\n\n#### Building the `controller` binary\n\n```bash\nmake controller\n```\n\nThis builds the `controller` binary in the working directory.\n\n#### Running unit tests\n\nTo run the unit tests for `controller` binary:\n\n```bash\nmake test\n```\n\n#### Push the controller image\n\nThis would work with a local minikube setup to build the controller:\n\n```bash\nmake K8S_CONTEXT=minikube OS=linux ARCH=amd64 push-controller\n```\n\nIt will not push, as minikube accesses local docker images directly.\n\nRemember the `REGISTRY` env var is needed when using a custom registry:\n\n```bash\nmake K8S_CONTEXT=kind REGISTRY=localhost:5000 OS=linux ARCH=amd64 push-controller\n```\n\nThis builds the controller container image and pushes it.\n\n#### Building & applying the controller manifests\n\n```bash\nmake K8S_CONTEXT=minikube apply-controller-manifests\n```\n\nOr for `kind`:\n\n```bash\nmake K8S_CONTEXT=kind REGISTRY=localhost:5000 apply-controller-manifests\n```\n\nThis builds the controller K8s manifests in the working directory and deploys them.\n\n#### Running integration tests\n\n```bash\nmake integrationtest\n```\n"
  },
  {
    "path": "docs/developer/crypto.md",
    "content": "# Cryptographic documentation\n\n## Protocols and cryptographic tools used\n\nSealed-secrets uses the following protocols for the secret management:\n\n- **AES-256-GCM** with a randomly generated single-use 32 bytes session key. Since the key is single-use, we do not use any nonce. The key is used to encrypt the secret, ensuring its confidentiality and integrity.\n- **RSA-OAEP**, with **SHA-256**. It is used to assure the confidentiality of the AES-256-GCM session key, following the *key encapsulation mechanism*.\n- **X509** certificates are used to manage RSA public keys. This public key contained in the certificate can be used to encrypt AES-256-GCM session key.\n\nCertificates generated by the sealed secrets controller are renewed every 30 days and have a 10 years validity span.\n\n## Entropy considerations\n\nThe golang API used for the entropy is `crypto/rand`. The following description can be found about the entropy generator used regarding the host system:\n\n```\n// On Linux, FreeBSD, Dragonfly and Solaris, Reader uses getrandom(2) if\n// available, /dev/urandom otherwise.\n// On OpenBSD and macOS, Reader uses getentropy(2).\n// On other Unix-like systems, Reader reads from /dev/urandom.\n// On Windows systems, Reader uses the RtlGenRandom API.\n// On Wasm, Reader uses the Web Crypto API.\n```\n\nThose cryptographic APIs are known to provide a good cryptographic entropy, and are not vulnerable to cryptographic attacks unless the seed is known.\n\nFor further information about those APIs:\n\n- [Linux/FreeBSD/Dragonfly/Solaris](https://linux.die.net/man/4/urandom)\n- [OpenBSD/macOS](https://www.freebsd.org/cgi/man.cgi?query=getentropy&sektion=3&format=html)\n- [Windows](https://download.microsoft.com/download/1/c/9/1c9813b8-089c-4fef-b2ad-ad80e79403ba/Whitepaper%20-%20The%20Windows%2010%20random%20number%20generation%20infrastructure.pdf)\n- [WASM](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues)\n\n## Functioning\n\n### Public/private key pair management\n\nThe controller looks for a cluster-wide private/public key pair on startup. If no key pair is found and none is provided manually, the controller generates a new 4096 bit (by default) RSA key pair. In both cases, the key pair is persisted in a regular Secret in the same namespace as the controller.\n\nThe public key (in the form of a self-signed certificate if it was generated by the controller) should be made publicly available to anyone wanting to use SealedSecrets with this cluster.\n\nNote that it is possible to use your own X509 certificate with the command bellow:\n\n```\nkubeseal --cert [https:/]/path/to/your-cert.pem\n```\n\nThe certificate is printed to the controller log at startup and is also available via an HTTP GET request to `/v1/cert.pem` on the controller.\n\n### Secret encryption\n\nThe secret is encrypted by AES-256-GCM with a randomly-generated single-use 32 byte session key.\n\nThe result of this operation will be called `AES encrypted data` in the next diagram, and the present step is the `1.`.\n\n### Session key encryption\n\nThe session key used by AES-256-GCM to encrypt the Secret is encapsulated with the controller's public key using RSA-OAEP with SHA256.\n\nThe OAEP input content, called `label` in the next diagram, differs depending on the sealed secret controller scope configuration. This algorithm is only used to encrypt the AES session key.\n\n- Default scope configuration : `label` is equal to the concatenation of the Secret's namespace and the Secret's name.\n- Namespace-wide scope configuration : `label` is equal to the Secret's namespace.\n- Cluster-wide scope configuration : `label` is empty.\n\nThe result of the RSA-OAEP encryption is called `RSA encrypted data` in the next diagram, and the present step is the `2.`.\n\n### Sealed Secret storage\n\nThe final Sealed Secret data format is the following (where `||` is the concatenation operator): `size of AES encrypted key (2 bytes) || RSA encrypted data || AES encrypted data`\n\n### Diagram to summarize\n\n```\n\t\t\t\t\t\t\t\tSecret\n                                                                   |\n                                                                   │\n                                                   K_s────────────►│\n                                                    │              │\n                                       K_pub───────►│              │\n                                                    │              │ 1.\n                                       label───────►│ 2.           │\n                                                    │              │\n                     ┌──────────────────────┬───────▼───────┬──────▼───────┐\nSealed Secret data = │size of AES encrypted │ RSA encrypted │ AES encrypted│\n                     │key (2 bytes)         │ data          │ data         │\n                     └──────────────────────┴───────────────┴──────────────┘\n\nK_s = 256 bits single-use session key, used by AES-GCM\nK_pub = Public key from the self-signed certificate, used by RSA-OAEP\nlabel = Additional input for RSA-OAEP encryption.\n        Content differs depending on the scope configuration:\n         * Default config : label = Secret's namespace || Secret's name\n         * Namespace-wide : label = Secret's namespace\n         * Cluster-wide : label is empty\n```\n\n### Decryption process\n\nThe decryption is simply the inversion of the encryption.\n\n`Size of AES encrypted key` is read and used to separate `RSA encrypted data` and `AES encrypted data` properly.\n\nThen the private key associated with the public key (see Session key encryption) is used with the `label` to decrypt the `RSA encrypted data`, effectively retrieving the AES session key.\n\nTo end this process, the `AES encrypted data` is decrypted using the AES session key, therefore unsealing the original Secret.\n\n# Post-quantum cryptography considerations\n\n## Entropy source\n\n### Analysis\n\nEven if QRNG (Quantum Random Number Generator) are considered better than PRNG (Pseudo Random Number Generator) in a quantum cryptography context as well as in a non-quantum context, QRNG relies on a quantum mechanical phenomenon. It requires a physical device, therefore QRNG usage is out of Sealed Secrets scope, which will stay on the `crypto/rand` usage.\n\n### Associated documentation\n\n[Combining a quantum random number generator and quantum-resistant algorithms into the GnuGPG open-source software](https://doi.org/10.1515/aot-2020-0021)\n\n## AES-256-GCM\n\n### Analysis\n\nAES-256-GCM is quantum resistant.\nGrover algorithm can reduce the bruteforce of the key from 2²⁵⁶ to 2¹²⁸ which is still considered very secure.\nNevertheless, since AES uses unchangeable 128 bits blocks, Grover algorithm can in some cases decrease the complexity of the bruteforce to 2⁶⁴.\n\n### Recommendations\n\nAES-256-GCM quantum security is not a concern.\nCases with a bruteforce complexity of 2⁶⁴ are unlikely for Sealed Secret considering how AES is used in the project.\nEven assuming that 2⁶⁴ bruteforce is likely, it can still be considered secure today (but not in the long run).\nA recommendation is to look for a AES replacement that provide 128 bits post-quantum cryptographic security in any cases, such as ChaCha20-Poly1305. Applying this recommendation is considered low priority.\n\n### Associated documentation\n\n[Quantum Security Analysis of AES](https://eprint.iacr.org/2019/272.pdf)\n\n[Critics on AES-256-GCM](https://soatok.blog/2020/05/13/why-aes-gcm-sucks/)\n\n[Security Analysis of ChaCha20-Poly1305 AEAD](https://www.cryptrec.go.jp/exreport/cryptrec-ex-2601-2016.pdf)\n\n\n## SHA-256\n\n### Analysis\n\nSHA-256 is quantum resistant.\nGrover Algorithm can reduce the bruteforce from 2²⁵⁶ to 2¹²⁸ which is considered very secure.\nIt is computationally cheaper to use a non-quantum algorithm to generate a collision than to employ a quantum computer.\n\n### Recommendations\n\nNo recommendations about SHA-256.\n\n### Associated documentation\n[Cost analysis of hash collisions: Will quantum computers make SHARCS obsolete?](https://cr.yp.to/hash/collisioncost-20090823.pdf)\n\n## RSA-OAEP\n\n### Analysis\n\nRSA-OAEP, as any RSA algorithm, **is not quantum resistant**.\nShor algorithm can be used to solve in a reasonable time 3 mathematical problems on which RSA cryptography is based on: integer factorization problem, the discrete logarithm problem and the elliptic-curve discrete logarithm problem. Therefore, RSA-OAEP is easily breakable for an attacker with quantum capability.\n\n### Recommendations\n\nReplace RSA whenever feasible. This recommendation must be the highest priority regarding the post-quantum security of Sealed Secrets.\nThere are three serious candidates to use instead of RSA: LMS and XMSS, which are Lattice-based, and McEliece with random Goppa codes, which is code-based and relies on SDP (Syndrome Decoding Problem).\nThose three algorithms are serious candidates for RSA replacement and the choice must be done carefully, without forgetting to study other algorithms such as NTRU.\n\nIt is important to qualify this recommendation with a couple of prerequisites:\n- A standard or clear recommended Public Key Cryptography Algorithm replacement emerges in the industry.\n- A reliable Go implementation is available in a compatible Open Source license.\n\nWithout such prerequisites in place, an RSA replacement cannot be commited upon.\n\n### Associated documentation\n\n[LMS](https://datatracker.ietf.org/doc/html/rfc8554)\n\n[XMSS](https://datatracker.ietf.org/doc/html/rfc8391)\n\n[Lattice-based cryptography](https://en.wikipedia.org/wiki/Lattice-based_cryptography)\n\n[McEliece](https://ipnpr.jpl.nasa.gov/progress_report2/42-44/44N.PDF)\n\n[Syndrome Decoding Problem](https://en.wikipedia.org/wiki/Decoding_methods#Syndrome_decoding)\n\n[NIST on post-quantum algorithms](https://csrc.nist.gov/Projects/post-quantum-cryptography/round-3-submissions)\n\n[Quantum-Resistant Cryptography](https://arxiv.org/ftp/arxiv/papers/2112/2112.00399.pdf)\n\n"
  },
  {
    "path": "docs/developer/kubeseal.md",
    "content": "# Kubeseal Developer Guide\n\nKubeseal component is a CLI tool that uses asymmetric crypto to encrypt secrets that only the controller can decrypt.\n\n## Download the Kubeseal source code\n\n```bash\ngit clone https://github.com/bitnami-labs/sealed-secrets.git $SEALED_SECRETS_DIR\n```\n\nThe kubeseal sources are located under `cmd/kubeseal/` and use packages from the `pkg` directory.\n\n### Building the `kubeseal` binary\n\n```bash\nmake kubeseal\n```\n\nThis builds the `kubeseal` binary in the working directory.\n\n### Running tests\n\nTo run the unit tests for `kubeseal` binary:\n\n```bash\nmake test\n```\n"
  },
  {
    "path": "docs/developer/swagger.yml",
    "content": "openapi: 3.0.3\ninfo:\n  title: Sealed Secrets Controller\n  description: Sealed Secrets are \"one-way\" encrypted K8s Secrets that can be created by anyone, but can only be decrypted by the controller running in the target cluster, recovering the original Secret object.\n  version: 0.17.3\nservers:\n  - url: http://sealed-secrets-controller/v1\n    description: Sealed Secrets Controller API\npaths:\n  /cert.pem:\n    get:\n      summary: Get public key certificate to use to sign Sealed Secrets\n      responses:\n        200:\n          description: Certificate\n          content:\n            application/x-pem-file:\n              schema:\n                type: string\n                format: binary\n        500:\n          description: Internal Server Error\n        default:\n          description: Unexpected error\n  /verify:\n    post:\n      summary: Validate Sealed Secrets object\n      requestBody:\n        required: true\n        content:\n          application/json:\n            schema:\n              $ref: '#/components/schemas/SealedSecret'\n      responses:\n        200:\n          description: Validation OK\n        400:\n          description: Bad Request\n        409:\n          description: Conflict\n        500:\n          description: Internal Server Error\n  /rotate:\n    post:\n      summary: Re-encrypt Sealed Secrets object\n      requestBody:\n        required: true\n        content:\n          application/json:\n            schema:\n              $ref: '#/components/schemas/SealedSecret'\n      responses:\n        200:\n          description: Sealed Secrets re-encrypted\n          content:\n            application/json:\n              schema:\n                $ref: '#/components/schemas/SealedSecret'\n        400:\n          description: Bad Request\n        409:\n          description: Conflict\n        500:\n          description: Internal Server Error\ncomponents:\n  schemas:\n    SealedSecret:\n      type: object\n      properties:\n        apiVersion:\n          type: string\n          description: Sealed Secrets API version\n          example: bitnami.com/v1alpha1\n        kind:\n          type: string\n          description: Sealed Secrets Object Type\n          example: SealedSecret\n        metadata:\n          $ref: \"#/components/schemas/Metadata\"\n        spec:\n          $ref: \"#/components/schemas/Spec\"\n      required:\n        - apiVersion\n        - kind\n    Metadata:\n      type: object\n      description: Sealed Secrets Object metadata\n      properties:\n        name:\n          type: string\n          description: Sealed Secrets Object name\n          example: secret-name\n        namespace:\n          type: string\n          description: Sealed Secrets Object namespace\n          example: default\n        creationTimestamp:\n          type: string\n          description: Sealed Secrets Object creation stamp\n      required:\n        - name\n        - namespace\n    Spec:\n      type: object\n      description: Sealed Secrets spec\n      properties:\n        encryptedData:\n          type: object\n          description: Sealed Secrets encrypted data\n        template:\n          type: object\n          description: Sealed Secrets template\n          properties:\n            metadata:\n              $ref: \"#/components/schemas/Metadata\"\n            data:\n              type: string\n              description: Sealed Secrets data\n"
  },
  {
    "path": "docs/examples/config-template/README.md",
    "content": "# Injecting secrets into config file templates\n\nKubernetes Secrets are very flexible and can be consumed in many ways.\nSecret values can be passed to containers as environment variables or appear as regular files when mounting secret volumes.\n\nOften users end up using the latter method just to wrap full configuration files into k8s secrets, just\nbecause one or more fields in the config file happen to be secrets (e.g. a database password, or a session cookie encryption key).\n\nIdeally you should avoid configuring your software that way, instead split your configuration from your secrets somehow. Also make sure you know about [12 Factor](https://www.12factor.net/).\n\nThat said, there are circumstances where you just have to provide such a file to your application (perhaps because it's a legacy app) and encrypting the whole configuration file in a single SealedSecrets item is problematic:\n\n- You cannot easily update individual secret values (e.g. rotate your DB password), without first decrypting the whole configuration file.\n- Since the whole configuration file is encrypted, it's hard to view, change (and review) non-secret parts of the config.\n\nThis example shows how to use built in support for templating encrypted secret values into a plaintext key template.\n\nTo update the encrypted data in the included SealedSecret with your own value for `server1` you can run:\n\n```bash\necho -n baz | kubectl create secret generic example --dry-run=client --from-file=server1=/dev/stdin -o json \\\n  | kubeseal -o yaml --merge-into sealedsecret.yaml\n```\n"
  },
  {
    "path": "docs/examples/config-template/deployment.yaml",
    "content": "---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: example\nspec:\n  selector:\n    matchLabels:\n      app: example\n  template:\n    metadata:\n      labels:\n        app: example\n    spec:\n      containers:\n      # This is an example application that assumes a complex configuration file in /config/myconfig.json.\n      # The JSON format here is just an example; any textual file format would work.\n      - name: app\n        image: bitnami/minideb:buster\n        volumeMounts:\n        - name: config-volume\n          mountPath: /config\n        command: [\"sh\", \"-c\"]\n        args:\n        - |\n         echo Your app will get this config file:\n         cat /config/myconfig.json\n         # dummy application\n         sleep 100000h\n      volumes:\n      - name: config-volume\n        secret:\n          secretName: example\n"
  },
  {
    "path": "docs/examples/config-template/sealedsecret.yaml",
    "content": "--- \napiVersion: bitnami.com/v1alpha1\nkind: SealedSecret\nmetadata:\n  name: example\nspec:\n  encryptedData:\n    server1: \"AgA+dujiBo...\"\n    server2: \"AgCC4/FvhZ...\"\n    database1: \"AgBtYQzl3T...\"\n  template:\n    metadata:\n      creationTimestamp: null\n      name: example\n      namespace: default\n    data:\n      myconfig.json: |\n        {\n               \"Server\": [{\n                               \"host\": \"foobar\",\n                               \"ip\": \"10.10.10.12\",\n                               \"port\": \"22\",\n                               \"env\": \"SOME_ENV\",\n                               \"user\": \"myuser\",\n                               \"password\": {{ toJson .server1 }},\n                               \"role\": \"foo\"\n                       },{\n                               \"host\": \"barfoo\",\n                               \"ip\": \"10.10.10.11\",\n                               \"port\": \"22\",\n                               \"env\": \"SOME_OTHER_STUFF\",\n                               \"user\": \"otheruser\",\n                               \"password\": {{ toJson .server2 }},\n                               \"role\": \"foo\"\n                       }\n               ],\n               \"Database\": [{\n                               \"host\": \"somedb\",\n                               \"ip\": \"10.10.10.10\",\n                               \"port\": \"1521\",\n                               \"sid\": \"FOO\",\n                               \"env\": \"BAZ\",\n                               \"user\": \"abcdefg123\",\n                               \"password\": {{ toJson .database1 }},\n                               \"role\": \"foo\"\n                       }\n               ]\n        }\n"
  },
  {
    "path": "githooks/pre-commit/doc-toc",
    "content": "#!/bin/bash\n\ncurrent_branch=\"$(git rev-parse --abbrev-ref HEAD)\"\norigin_commit=\"$(git rev-parse --short \"$(git merge-base main \"$current_branch\")\")\"\nfiles_to_commit=\"$(git diff --name-only \"$origin_commit\")\"\n\nfor f in $(uniq <<< \"$files_to_commit\"); do\n    if [[ \"$(basename $f)\" =~ .*\\.(md|txt) ]]; then\n        doctoc \"$f\"\n    fi\ndone\nif [[ $(git status --porcelain --untracked-files=no | wc -l) -gt 0 ]]; then\n    git add --all\n    git status --short -uno\nfi\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/bitnami-labs/sealed-secrets\n\ngo 1.26.1\n\nrequire (\n\tgithub.com/Masterminds/sprig/v3 v3.3.0\n\tgithub.com/google/go-cmp v0.7.0\n\tgithub.com/google/renameio v0.1.0\n\tgithub.com/mattn/go-isatty v0.0.20\n\tgithub.com/mkmik/multierror v0.4.0\n\tgithub.com/onsi/ginkgo/v2 v2.28.1\n\tgithub.com/onsi/gomega v1.39.1\n\tgithub.com/prometheus/client_golang v1.23.2\n\tgithub.com/prometheus/client_model v0.6.2\n\tgithub.com/spf13/pflag v1.0.10\n\tgithub.com/throttled/throttled v2.2.5+incompatible\n\tgolang.org/x/crypto v0.49.0\n\tgopkg.in/yaml.v2 v2.4.0\n\tk8s.io/api v0.35.2\n\tk8s.io/apimachinery v0.35.2\n\tk8s.io/client-go v0.35.2\n\tk8s.io/code-generator v0.35.2\n\tk8s.io/klog v1.0.0\n\tk8s.io/klog/v2 v2.140.0\n\tk8s.io/utils v0.0.0-20251002143259-bc988d571ff4\n)\n\nrequire (\n\tdario.cat/mergo v1.0.1 // indirect\n\tgithub.com/Masterminds/goutils v1.1.1 // indirect\n\tgithub.com/Masterminds/semver/v3 v3.4.0 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/emicklei/go-restful/v3 v3.12.2 // indirect\n\tgithub.com/fxamacker/cbor/v2 v2.9.0 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.21.0 // indirect\n\tgithub.com/go-openapi/jsonreference v0.21.0 // indirect\n\tgithub.com/go-openapi/swag v0.23.0 // indirect\n\tgithub.com/go-task/slim-sprig/v3 v3.0.0 // indirect\n\tgithub.com/gomodule/redigo v2.0.0+incompatible // indirect\n\tgithub.com/google/gnostic-models v0.7.0 // indirect\n\tgithub.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/hashicorp/golang-lru v1.0.2 // indirect\n\tgithub.com/huandu/xstrings v1.5.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/mitchellh/copystructure v1.2.0 // indirect\n\tgithub.com/mitchellh/reflectwalk v1.0.2 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/prometheus/common v0.66.1 // indirect\n\tgithub.com/prometheus/procfs v0.16.1 // indirect\n\tgithub.com/shopspring/decimal v1.4.0 // indirect\n\tgithub.com/spf13/cast v1.7.0 // indirect\n\tgithub.com/x448/float16 v0.8.4 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.3 // indirect\n\tgo.yaml.in/yaml/v3 v3.0.4 // indirect\n\tgolang.org/x/mod v0.33.0 // indirect\n\tgolang.org/x/net v0.51.0 // indirect\n\tgolang.org/x/oauth2 v0.30.0 // indirect\n\tgolang.org/x/sync v0.20.0 // indirect\n\tgolang.org/x/sys v0.42.0 // indirect\n\tgolang.org/x/term v0.41.0 // indirect\n\tgolang.org/x/text v0.35.0 // indirect\n\tgolang.org/x/time v0.9.0 // indirect\n\tgolang.org/x/tools v0.42.0 // indirect\n\tgoogle.golang.org/protobuf v1.36.8 // indirect\n\tgopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tk8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect\n\tk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect\n\tsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect\n\tsigs.k8s.io/randfill v1.0.0 // indirect\n\tsigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect\n\tsigs.k8s.io/yaml v1.6.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=\ndario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=\ngithub.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=\ngithub.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=\ngithub.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=\ngithub.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=\ngithub.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=\ngithub.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=\ngithub.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.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=\ngithub.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=\ngithub.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=\ngithub.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=\ngithub.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=\ngithub.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=\ngithub.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=\ngithub.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=\ngithub.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=\ngithub.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=\ngithub.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE=\ngithub.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=\ngithub.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=\ngithub.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=\ngithub.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=\ngithub.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=\ngithub.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=\ngithub.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=\ngithub.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=\ngithub.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=\ngithub.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=\ngithub.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=\ngithub.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=\ngithub.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=\ngithub.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=\ngithub.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=\ngithub.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=\ngithub.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=\ngithub.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=\ngithub.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=\ngithub.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=\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/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=\ngithub.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=\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/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=\ngithub.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=\ngithub.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=\ngithub.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE=\ngithub.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A=\ngithub.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=\ngithub.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=\ngithub.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=\ngithub.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=\ngithub.com/mkmik/multierror v0.4.0 h1:TcH9HTFK/X1JJLOnWYp0b6mKQJuVUGwS9aFFGBfYaH8=\ngithub.com/mkmik/multierror v0.4.0/go.mod h1:pz+UajC3ELc35PsCPVL69CAji3J/YNRuyI4rOYdCwPY=\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/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/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/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=\ngithub.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=\ngithub.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=\ngithub.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=\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.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=\ngithub.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=\ngithub.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=\ngithub.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=\ngithub.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=\ngithub.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=\ngithub.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=\ngithub.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=\ngithub.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=\ngithub.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=\ngithub.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=\ngithub.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=\ngithub.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=\ngithub.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=\ngithub.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=\ngithub.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/throttled/throttled v2.2.5+incompatible h1:65UB52X0qNTYiT0Sohp8qLYVFwZQPDw85uSa65OljjQ=\ngithub.com/throttled/throttled v2.2.5+incompatible/go.mod h1:0BjlrEGQmvxps+HuXLsyRdqpSRvJpq0PNIsOtqP9Nos=\ngithub.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=\ngithub.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=\ngithub.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=\ngithub.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=\ngithub.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=\ngithub.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=\ngithub.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=\ngithub.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=\ngithub.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=\ngithub.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=\ngo.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=\ngo.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=\ngo.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=\ngolang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=\ngolang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=\ngolang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=\ngolang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=\ngolang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=\ngolang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=\ngolang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=\ngolang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=\ngolang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=\ngolang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=\ngolang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=\ngolang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=\ngolang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=\ngolang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=\ngolang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=\ngolang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=\ngolang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=\ngolang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=\ngolang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=\ngolang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=\ngolang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=\ngolang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=\ngolang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=\ngoogle.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=\ngoogle.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=\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/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=\ngopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=\ngopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nk8s.io/api v0.35.2 h1:tW7mWc2RpxW7HS4CoRXhtYHSzme1PN1UjGHJ1bdrtdw=\nk8s.io/api v0.35.2/go.mod h1:7AJfqGoAZcwSFhOjcGM7WV05QxMMgUaChNfLTXDRE60=\nk8s.io/apimachinery v0.35.2 h1:NqsM/mmZA7sHW02JZ9RTtk3wInRgbVxL8MPfzSANAK8=\nk8s.io/apimachinery v0.35.2/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=\nk8s.io/client-go v0.35.2 h1:YUfPefdGJA4aljDdayAXkc98DnPkIetMl4PrKX97W9o=\nk8s.io/client-go v0.35.2/go.mod h1:4QqEwh4oQpeK8AaefZ0jwTFJw/9kIjdQi0jpKeYvz7g=\nk8s.io/code-generator v0.35.2 h1:3874swbO2c26VWTf6lKD4NWGyHIfyBeTCk7caCG3TuU=\nk8s.io/code-generator v0.35.2/go.mod h1:id4XLCm0yAQq5nlvyfAKibMOKnMjzlesAwGw6kM3Adc=\nk8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b h1:gMplByicHV/TJBizHd9aVEsTYoJBnnUAT5MHlTkbjhQ=\nk8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b/go.mod h1:CgujABENc3KuTrcsdpGmrrASjtQsWCT7R99mEV4U/fM=\nk8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=\nk8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=\nk8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=\nk8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=\nk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=\nk8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=\nk8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=\nk8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=\nsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=\nsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=\nsigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=\nsigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=\nsigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=\nsigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=\nsigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=\nsigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=\n"
  },
  {
    "path": "hack/boilerplate.go.txt",
    "content": ""
  },
  {
    "path": "hack/tools.go",
    "content": "// This file forces go mod to include dependencies used during build, such as\n// code generation tools.\n// The build tag below ensures this dep is not pulled during normal builds.\n\n//go:build tools\n// +build tools\n\npackage tools\n\nimport (\n\t_ \"k8s.io/code-generator\"\n)\n"
  },
  {
    "path": "hack/update-codegen.sh",
    "content": "#!/usr/bin/env bash\n\nset -o errexit\nset -o nounset\nset -o pipefail\n\nSCRIPT_ROOT=$(dirname \"${BASH_SOURCE[0]}\")/..\nCODEGEN_PKG=${CODEGEN_PKG:-$(cd \"${SCRIPT_ROOT}\"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo ../code-generator)}\n\nsource \"${CODEGEN_PKG}/kube_codegen.sh\"\n\nTHIS_PKG=\"github.com/bitnami-labs/sealed-secrets\"\n\nkube::codegen::gen_helpers \\\n    --boilerplate \"${SCRIPT_ROOT}/hack/boilerplate.go.txt\" \\\n    \"${SCRIPT_ROOT}/pkg/apis\"\n\nkube::codegen::gen_client \\\n    --with-watch \\\n    --output-dir \"${SCRIPT_ROOT}/pkg/client\" \\\n    --output-pkg \"${THIS_PKG}/pkg/client\" \\\n    --boilerplate \"${SCRIPT_ROOT}/hack/boilerplate.go.txt\" \\\n    \"${SCRIPT_ROOT}/pkg/apis\"\n"
  },
  {
    "path": "helm/sealed-secrets/.helmignore",
    "content": "# Patterns to ignore when building packages.\n# This supports shell glob matching, relative path matching, and\n# negation (prefixed with !). Only one pattern per line.\n.DS_Store\n# Common VCS dirs\n.git/\n.gitignore\n.bzr/\n.bzrignore\n.hg/\n.hgignore\n.svn/\n# Common backup files\n*.swp\n*.bak\n*.tmp\n*~\n# Various IDEs\n.project\n.idea/\n*.tmproj\n"
  },
  {
    "path": "helm/sealed-secrets/Chart.yaml",
    "content": "annotations:\n  category: DeveloperTools\napiVersion: v2\nappVersion: 0.36.1\ndescription: Helm chart for the sealed-secrets controller.\nhome: https://github.com/bitnami-labs/sealed-secrets\nicon: https://bitnami.com/assets/stacks/sealed-secrets/img/sealed-secrets-stack-220x234.png\nkeywords:\n  - secrets\n  - sealed-secrets\nkubeVersion: \">=1.16.0-0\"\nmaintainers:\n  - name: Bitnami\n    url: https://github.com/bitnami-labs/sealed-secrets\nname: sealed-secrets\ntype: application\nversion: 2.18.4\nsources:\n  - https://github.com/bitnami-labs/sealed-secrets\n"
  },
  {
    "path": "helm/sealed-secrets/README.md",
    "content": "# Sealed Secrets\n\nSealed Secrets are \"one-way\" encrypted K8s Secrets that can be created by anyone, but can only be decrypted by the controller running in the target cluster recovering the original object.\n\n<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->\n\n- [TL;DR](#tldr)\n- [Introduction](#introduction)\n- [Prerequisites](#prerequisites)\n- [Installing the Chart](#installing-the-chart)\n- [Uninstalling the Chart](#uninstalling-the-chart)\n- [Parameters](#parameters)\n  - [Common parameters](#common-parameters)\n  - [Sealed Secrets Parameters](#sealed-secrets-parameters)\n  - [Traffic Exposure Parameters](#traffic-exposure-parameters)\n  - [Other Parameters](#other-parameters)\n  - [Metrics parameters](#metrics-parameters)\n- [Using kubeseal](#using-kubeseal)\n- [Configuration and installation details](#configuration-and-installation-details)\n- [Troubleshooting](#troubleshooting)\n- [Upgrading](#upgrading)\n  - [To 2.0.0](#to-200)\n\n<!-- END doctoc generated TOC please keep comment here to allow auto-update -->\n\n## TL;DR\n\n```console\n$ helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets\n$ helm install my-release sealed-secrets/sealed-secrets\n```\n\n## Introduction\n\nBitnami charts for Helm are carefully engineered, actively maintained and are the quickest and easiest way to deploy containers on a Kubernetes cluster that are ready to handle production workloads.\n\nThis chart bootstraps a [Sealed Secret Controller](https://github.com/bitnami-labs/sealed-secrets) Deployment in [Kubernetes](http://kubernetes.io) using the [Helm](https://helm.sh) package manager.\n\nBitnami charts can be used with [Kubeapps](https://kubeapps.com/) for the deployment and management of Helm Charts in clusters.\n\n## Prerequisites\n\n- Kubernetes 1.16+\n- Helm 3.1.0\n\n## Installing the Chart\n\nTo install the chart with the release name `my-release`:\n\n```console\nhelm install my-release sealed-secrets/sealed-secrets\n```\n\nThe command deploys the Sealed Secrets controller on the Kubernetes cluster in the default configuration. The [Parameters](#parameters) section lists the parameters that can be configured during installation.\n\n> **Tip**: List all releases using `helm list`\n\n## Uninstalling the Chart\n\nTo uninstall/delete the `my-release` deployment:\n\n```console\nhelm delete my-release\n```\n\nThe command removes all the Kubernetes components associated with the chart and deletes the release.\n\n## Parameters\n\n### Common parameters\n\n| Name                | Description                                             | Value |\n| ------------------- | ------------------------------------------------------- | ----- |\n| `kubeVersion`       | Override Kubernetes version                             | `\"\"`  |\n| `nameOverride`      | String to partially override sealed-secrets.fullname    | `\"\"`  |\n| `fullnameOverride`  | String to fully override sealed-secrets.fullname        | `\"\"`  |\n| `namespace`         | Namespace where to deploy the Sealed Secrets controller | `\"\"`  |\n| `extraDeploy`       | Array of extra objects to deploy with the release       | `[]`  |\n| `commonAnnotations` | Annotations to add to all deployed resources            | `{}`  |\n| `commonLabels`      | Labels to add to all deployed resources                 | `{}`  |\n\n### Sealed Secrets Parameters\n\n| Name                                              | Description                                                                                                        | Value                               |\n| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ----------------------------------- |\n| `image.registry`                                  | Sealed Secrets image registry                                                                                      | `docker.io`                         |\n| `image.repository`                                | Sealed Secrets image repository                                                                                    | `bitnami/sealed-secrets-controller` |\n| `image.tag`                                       | Sealed Secrets image tag (immutable tags are recommended)                                                          | `0.36.1`                            |\n| `image.pullPolicy`                                | Sealed Secrets image pull policy                                                                                   | `IfNotPresent`                      |\n| `image.pullSecrets`                               | Sealed Secrets image pull secrets                                                                                  | `[]`                                |\n| `revisionHistoryLimit`                            | Number of old history to retain to allow rollback (If not set, default Kubernetes value is set to 10)              | `\"\"`                                |\n| `createController`                                | Specifies whether the Sealed Secrets controller should be created                                                  | `true`                              |\n| `secretName`                                      | The name of an existing TLS secret containing the key used to encrypt secrets                                      | `sealed-secrets-key`                |\n| `updateStatus`                                    | Specifies whether the Sealed Secrets controller should update the status subresource                               | `true`                              |\n| `skipRecreate`                                    | Specifies whether the Sealed Secrets controller should skip recreating removed secrets                             | `false`                             |\n| `keyrenewperiod`                                  | Specifies key renewal period. Default 30 days                                                                      | `\"\"`                                |\n| `keyttl`                                          | Specifies the certificate validity duration. Default 10 years.                                                     | `\"\"`                                |\n| `keycutofftime`                                   | Specifies a date at which the controller should generate a new certificate. Useful in early key renewal scenarios. | `\"\"`                                |\n| `rateLimit`                                       | Number of allowed sustained request per second for verify endpoint                                                 | `\"\"`                                |\n| `rateLimitBurst`                                  | Number of requests allowed to exceed the rate limit per second for verify endpoint                                 | `\"\"`                                |\n| `additionalNamespaces`                            | List of namespaces used to manage the Sealed Secrets                                                               | `[]`                                |\n| `privateKeyAnnotations`                           | Map of annotations to be set on the sealing keypairs                                                               | `{}`                                |\n| `privateKeyLabels`                                | Map of labels to be set on the sealing keypairs                                                                    | `{}`                                |\n| `logInfoStdout`                                   | Specifies whether the Sealed Secrets controller will log info to stdout                                            | `false`                             |\n| `logLevel`                                        | Specifies log level of controller (INFO,ERROR)                                                                     | `\"\"`                                |\n| `logFormat`                                       | Specifies log format (text,json)                                                                                   | `\"\"`                                |\n| `maxRetries`                                      | Number of maximum retries                                                                                          | `\"\"`                                |\n| `watchForSecrets`                                 | Specifies whether the Sealed Secrets controller will watch for new secrets                                         | `false`                             |\n| `kubeClientQPS`                                   | Kubeclient QPS (negative value disables ratelimiting)                                                              | `\"\"`                                |\n| `kubeClientBurst`                                 | Kubeclient Burst                                                                                                   | `\"\"`                                |\n| `command`                                         | Override default container command                                                                                 | `[]`                                |\n| `args`                                            | Override default container args                                                                                    | `[]`                                |\n| `livenessProbe.enabled`                           | Enable livenessProbe on Sealed Secret containers                                                                   | `true`                              |\n| `livenessProbe.initialDelaySeconds`               | Initial delay seconds for livenessProbe                                                                            | `0`                                 |\n| `livenessProbe.periodSeconds`                     | Period seconds for livenessProbe                                                                                   | `10`                                |\n| `livenessProbe.timeoutSeconds`                    | Timeout seconds for livenessProbe                                                                                  | `1`                                 |\n| `livenessProbe.failureThreshold`                  | Failure threshold for livenessProbe                                                                                | `3`                                 |\n| `livenessProbe.successThreshold`                  | Success threshold for livenessProbe                                                                                | `1`                                 |\n| `readinessProbe.enabled`                          | Enable readinessProbe on Sealed Secret containers                                                                  | `true`                              |\n| `readinessProbe.initialDelaySeconds`              | Initial delay seconds for readinessProbe                                                                           | `0`                                 |\n| `readinessProbe.periodSeconds`                    | Period seconds for readinessProbe                                                                                  | `10`                                |\n| `readinessProbe.timeoutSeconds`                   | Timeout seconds for readinessProbe                                                                                 | `1`                                 |\n| `readinessProbe.failureThreshold`                 | Failure threshold for readinessProbe                                                                               | `3`                                 |\n| `readinessProbe.successThreshold`                 | Success threshold for readinessProbe                                                                               | `1`                                 |\n| `startupProbe.enabled`                            | Enable startupProbe on Sealed Secret containers                                                                    | `false`                             |\n| `startupProbe.initialDelaySeconds`                | Initial delay seconds for startupProbe                                                                             | `0`                                 |\n| `startupProbe.periodSeconds`                      | Period seconds for startupProbe                                                                                    | `10`                                |\n| `startupProbe.timeoutSeconds`                     | Timeout seconds for startupProbe                                                                                   | `1`                                 |\n| `startupProbe.failureThreshold`                   | Failure threshold for startupProbe                                                                                 | `3`                                 |\n| `startupProbe.successThreshold`                   | Success threshold for startupProbe                                                                                 | `1`                                 |\n| `customLivenessProbe`                             | Custom livenessProbe that overrides the default one                                                                | `{}`                                |\n| `customReadinessProbe`                            | Custom readinessProbe that overrides the default one                                                               | `{}`                                |\n| `customStartupProbe`                              | Custom startupProbe that overrides the default one                                                                 | `{}`                                |\n| `resources.limits`                                | The resources limits for the Sealed Secret containers                                                              | `{}`                                |\n| `resources.requests`                              | The requested resources for the Sealed Secret containers                                                           | `{}`                                |\n| `podSecurityContext.enabled`                      | Enabled Sealed Secret pods' Security Context                                                                       | `true`                              |\n| `podSecurityContext.fsGroup`                      | Set Sealed Secret pod's Security Context fsGroup                                                                   | `65534`                             |\n| `containerSecurityContext.enabled`                | Enabled Sealed Secret containers' Security Context                                                                 | `true`                              |\n| `containerSecurityContext.readOnlyRootFilesystem` | Whether the Sealed Secret container has a read-only root filesystem                                                | `true`                              |\n| `containerSecurityContext.runAsNonRoot`           | Indicates that the Sealed Secret container must run as a non-root user                                             | `true`                              |\n| `containerSecurityContext.runAsUser`              | Set Sealed Secret containers' Security Context runAsUser                                                           | `1001`                              |\n| `containerSecurityContext.capabilities`           | Adds and removes POSIX capabilities from running containers (see `values.yaml`)                                    |                                     |\n| `podLabels`                                       | Extra labels for Sealed Secret pods                                                                                | `{}`                                |\n| `podAnnotations`                                  | Annotations for Sealed Secret pods                                                                                 | `{}`                                |\n| `priorityClassName`                               | Sealed Secret pods' priorityClassName                                                                              | `\"\"`                                |\n| `runtimeClassName`                                | Sealed Secret pods' runtimeClassName                                                                               | `\"\"`                                |\n| `affinity`                                        | Affinity for Sealed Secret pods assignment                                                                         | `{}`                                |\n| `nodeSelector`                                    | Node labels for Sealed Secret pods assignment                                                                      | `{}`                                |\n| `tolerations`                                     | Tolerations for Sealed Secret pods assignment                                                                      | `[]`                                |\n| `additionalVolumes`                               | Extra Volumes for the Sealed Secrets Controller Deployment                                                         | `{}`                                |\n| `additionalVolumeMounts`                          | Extra volumeMounts for the Sealed Secrets Controller container                                                     | `{}`                                |\n| `hostNetwork`                                     | Sealed Secrets pods' hostNetwork                                                                                   | `false`                             |\n| `containerPorts.http`                             | Controller HTTP Port on the Host and Container                                                                     | `8080`                              |\n| `containerPorts.metrics`                          | Metrics HTTP Port on the Host and Container                                                                        | `8081`                              |\n| `hostPorts.http`                                  | Controller HTTP Port on the Host                                                                                   | `\"\"`                                |\n| `hostPorts.metrics`                               | Metrics HTTP Port on the Host                                                                                      | `\"\"`                                |\n| `dnsPolicy`                                       | Sealed Secrets pods' dnsPolicy                                                                                     | `\"\"`                                |\n\n### Traffic Exposure Parameters\n\n| Name                               | Description                                                                                                                      | Value                    |\n| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |\n| `service.type`                     | Sealed Secret service type                                                                                                       | `ClusterIP`              |\n| `service.loadBalancerClass`        | Sealed Secret service loadBalancerClass                                                                                          | `\"\"`                     |\n| `service.port`                     | Sealed Secret service HTTP port                                                                                                  | `8080`                   |\n| `service.nodePort`                 | Node port for HTTP                                                                                                               | `\"\"`                     |\n| `service.annotations`              | Additional custom annotations for Sealed Secret service                                                                          | `{}`                     |\n| `ingress.enabled`                  | Enable ingress record generation for Sealed Secret                                                                               | `false`                  |\n| `ingress.pathType`                 | Ingress path type                                                                                                                | `ImplementationSpecific` |\n| `ingress.apiVersion`               | Force Ingress API version (automatically detected if not set)                                                                    | `\"\"`                     |\n| `ingress.ingressClassName`         | IngressClass that will be be used to implement the Ingress                                                                       | `\"\"`                     |\n| `ingress.hostname`                 | Default host for the ingress record                                                                                              | `sealed-secrets.local`   |\n| `ingress.path`                     | Default path for the ingress record                                                                                              | `/v1/cert.pem`           |\n| `ingress.annotations`              | Additional annotations for the Ingress resource. To enable certificate autogeneration, place here your cert-manager annotations. | `{}`                     |\n| `ingress.tls`                      | Enable TLS configuration for the host defined at `ingress.hostname` parameter                                                    | `false`                  |\n| `ingress.selfSigned`               | Create a TLS secret for this ingress record using self-signed certificates generated by Helm                                     | `false`                  |\n| `ingress.extraHosts`               | An array with additional hostname(s) to be covered with the ingress record                                                       | `[]`                     |\n| `ingress.extraPaths`               | An array with additional arbitrary paths that may need to be added to the ingress under the main host                            | `[]`                     |\n| `ingress.extraTls`                 | TLS configuration for additional hostname(s) to be covered with this ingress record                                              | `[]`                     |\n| `ingress.secrets`                  | Custom TLS certificates as secrets                                                                                               | `[]`                     |\n| `networkPolicy.enabled`            | Specifies whether a NetworkPolicy should be created                                                                              | `false`                  |\n| `networkPolicy.egress.enabled`     | Specifies wheter a egress is set in the NetworkPolicy                                                                            | `false`                  |\n| `networkPolicy.egress.kubeapiCidr` | Specifies the kubeapiCidr, which is the only egress allowed. If not set, kubeapiCidr will be found using Helm lookup             | `\"\"`                     |\n| `networkPolicy.egress.kubeapiPort` | Specifies the kubeapiPort, which is the only egress allowed. If not set, kubeapiPort will be found using Helm lookup             | `\"\"`                     |\n\n### Other Parameters\n\n| Name                           | Description                                                                                              | Value                                                                                    |\n| ------------------------------ | -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |\n| `serviceAccount.annotations`   | Annotations for Sealed Secret service account                                                            | `{}`                                                                                     |\n| `serviceAccount.create`        | Specifies whether a ServiceAccount should be created                                                     | `true`                                                                                   |\n| `serviceAccount.labels`        | Extra labels to be added to the ServiceAccount                                                           | `{}`                                                                                     |\n| `serviceAccount.name`          | The name of the ServiceAccount to use.                                                                   | `\"\"`                                                                                     |\n| `rbac.create`                  | Specifies whether RBAC resources should be created                                                       | `true`                                                                                   |\n| `rbac.clusterRole`             | Specifies whether the Cluster Role resource should be created                                            | `true`                                                                                   |\n| `rbac.clusterRoleName`         | Specifies the name for the Cluster Role resource                                                         | `secrets-unsealer`                                                                       |\n| `rbac.namespacedRoles`         | Specifies whether the namespaced Roles should be created (in each of the specified additionalNamespaces) | `false`                                                                                  |\n| `rbac.namespacedRolesName`     | Specifies the name for the namespaced Role resource                                                      | `secrets-unsealer`                                                                       |\n| `rbac.labels`                  | Extra labels to be added to RBAC resources                                                               | `{}`                                                                                     |\n| `rbac.pspEnabled`              | PodSecurityPolicy                                                                                        | `false`                                                                                  |\n| `rbac.serviceProxier.create`   | Specifies whether to create the \"proxier\" role, to allow external users to access the SealedSecret API   | `true`                                                                                   |\n| `rbac.serviceProxier.bind`     | Specifies whether to create a RoleBinding for the \"proxier\" role                                         | `true`                                                                                   |\n| `rbac.serviceProxier.subjects` | Specifies the RBAC subjects to grant the \"proxier\" role to, in the created RoleBinding                   | <pre><code>- apiGroup: rbac.authorization.k8s.io<br>  kind: Group<br>  name: system:authenticated</code></pre> |\n\n### Metrics parameters\n\n| Name                                       | Description                                                                            | Value       |\n| ------------------------------------------ | -------------------------------------------------------------------------------------- | ----------- |\n| `metrics.serviceMonitor.enabled`           | Specify if a ServiceMonitor will be deployed for Prometheus Operator                   | `false`     |\n| `metrics.serviceMonitor.namespace`         | Namespace where Prometheus Operator is running in                                      | `\"\"`        |\n| `metrics.serviceMonitor.labels`            | Extra labels for the ServiceMonitor                                                    | `{}`        |\n| `metrics.serviceMonitor.annotations`       | Extra annotations for the ServiceMonitor                                               | `{}`        |\n| `metrics.serviceMonitor.interval`          | How frequently to scrape metrics                                                       | `\"\"`        |\n| `metrics.serviceMonitor.scrapeTimeout`     | Timeout after which the scrape is ended                                                | `\"\"`        |\n| `metrics.serviceMonitor.honorLabels`       | Specify if ServiceMonitor endPoints will honor labels                                  | `true`      |\n| `metrics.serviceMonitor.metricRelabelings` | Specify additional relabeling of metrics                                               | `[]`        |\n| `metrics.serviceMonitor.relabelings`       | Specify general relabeling                                                             | `[]`        |\n| `metrics.dashboards.create`                | Specifies whether a ConfigMap with a Grafana dashboard configuration should be created | `false`     |\n| `metrics.dashboards.labels`                | Extra labels to be added to the Grafana dashboard ConfigMap                            | `{}`        |\n| `metrics.dashboards.annotations`           | Annotations to be added to the Grafana dashboard ConfigMap                             | `{}`        |\n| `metrics.dashboards.namespace`             | Namespace where Grafana dashboard ConfigMap is deployed                                | `\"\"`        |\n| `metrics.service.type`                     | Sealed Secret Metrics service type                                                     | `ClusterIP` |\n| `metrics.service.loadBalancerClass`        | Sealed Secret Metrics service loadBalancerClass                                        | `\"\"`        |\n| `metrics.service.port`                     | Sealed Secret service Metrics HTTP port                                                | `8081`      |\n| `metrics.service.nodePort`                 | Node port for HTTP                                                                     | `\"\"`        |\n| `metrics.service.annotations`              | Additional custom annotations for Sealed Secret Metrics service                        | `{}`        |\n\n### PodDisruptionBudget Parameters\n\n| Name                 | Description                                                 | Value   |\n| -------------------- | ----------------------------------------------------------- | ------- |\n| `pdb.create`         | Specifies whether a PodDisruptionBudget should be created   | `false` |\n| `pdb.minAvailable`   | The minimum number of pods (non number to omit)             | `1`     |\n| `pdb.maxUnavailable` | The maximum number of unavailable pods (non number to omit) | `\"\"`    |\n\n\nSpecify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example,\n\n```console\n$ helm install my-release \\\n  --set resources.requests.cpu=25m \\\n    sealed-secrets/sealed-secrets\n```\n\nThe above command sets the `resources.requests.cpu` parameter to `25m`.\n\nAlternatively, a YAML file that specifies the values for the above parameters can be provided while installing the chart. For example,\n\n```console\nhelm install my-release -f values.yaml sealed-secrets/sealed-secrets\n```\n\n## Using kubeseal\n\nInstall the kubeseal CLI by downloading the binary from [sealed-secrets/releases](https://github.com/bitnami-labs/sealed-secrets/releases).\n\nFetch the public key by passing the release name and namespace:\n\n```bash\nkubeseal --fetch-cert \\\n--controller-name=my-release \\\n--controller-namespace=my-release-namespace \\\n> pub-cert.pem\n```\n\nRead about kubeseal usage on [sealed-secrets docs](https://github.com/bitnami-labs/sealed-secrets#usage).\n\nNOTE: the helm chart by default installs the controller with the name `sealed-secrets`, while the `kubeseal` command line interface (CLI) tries to access the controller with the name `sealed-secrets-controller`. You can explicitly pass `--controller-name` to the CLI:\n\n```bash\nkubeseal --controller-name sealed-secrets <args>\n```\n\nAlternatively, you can override `fullnameOverride` on the helm chart install.\n\n## Configuration and installation details\n\n- In the case that **serviceAccount.create** is `false` and **rbac.create** is `true` it is expected for a ServiceAccount with the name **serviceAccount.name** to exist _in the same namespace as this chart_ before the installation.\n- If **rbac.create** is `true, by default *clusterRoles* are created. To switch to namespaced *Roles*:\n  1. set the required namespaces in **additionalNamespaces**\n  2. set **rbac.clusterRole** to `false`\n  3. set **rbac.namespacedRoles** to `true`\n- If **serviceAccount.create** is `true` there cannot be an existing service account with the name **serviceAccount.name**.\n- If a secret with name **secretName** does not exist _in the same namespace as this chart_, then on install one will be created. If a secret already exists with this name the keys inside will be used.\n- OpenShift: unset the runAsUser and fsGroup like this when installing in a custom namespace:\n\n```yaml\npodSecurityContext:\n  fsGroup:\n\ncontainerSecurityContext:\n  runAsUser:\n```\n\n## Troubleshooting\n\nFind more information about how to deal with common errors related to Bitnami's Helm charts in [this troubleshooting guide](https://docs.bitnami.com/general/how-to/troubleshoot-helm-chart-issues).\n\n## Upgrading\n\n### To 2.0.0\n\nA major refactoring of the chart has been performed to adopt several common practices for Helm charts. Upgrades from previous chart versions should work, however, the values structure experienced several changes and you'll have to adapt your custom values/parameters so they're aligned with the new structure. For instance, these are a couple of examples:\n\n- `controller.create` renamed as `createController`.\n- `securityContext.*` parameters are deprecated in favor of `podSecurityContext.*`, and `containerSecurityContext.*` ones.\n- `image.repository` changed to `image.registry`/`image.repository`.\n- `ingress.hosts[0]` changed to `ingress.hostname`.\n\nConsult the [Parameters](#parameters) section to obtain more info about the available parameters.\n\n[On November 13, 2020, Helm v2 support was formally finished](https://github.com/helm/charts#status-of-the-project), this new major version is no longer compatible with Helm v2.\n"
  },
  {
    "path": "helm/sealed-secrets/crds/bitnami.com_sealedsecrets.yaml",
    "content": "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.15.0\n  name: sealedsecrets.bitnami.com\nspec:\n  group: bitnami.com\n  names:\n    kind: SealedSecret\n    listKind: SealedSecretList\n    plural: sealedsecrets\n    singular: sealedsecret\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .status.conditions[0].message\n      name: Status\n      type: string\n    - jsonPath: .status.conditions[0].status\n      name: Synced\n      type: string\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: |-\n          SealedSecret is the K8s representation of a \"sealed Secret\" - a\n          regular k8s Secret that has been sealed (encrypted) using the\n          controller's key.\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              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: SealedSecretSpec is the specification of a SealedSecret.\n            properties:\n              data:\n                description: Data is deprecated and will be removed eventually. Use\n                  per-value EncryptedData instead.\n                format: byte\n                type: string\n              encryptedData:\n                additionalProperties:\n                  type: string\n                type: object\n                x-kubernetes-preserve-unknown-fields: true\n              template:\n                description: |-\n                  Template defines the structure of the Secret that will be\n                  created from this sealed secret.\n                properties:\n                  data:\n                    additionalProperties:\n                      type: string\n                    description: Keys that should be templated using decrypted data.\n                    nullable: true\n                    type: object\n                  immutable:\n                    description: |-\n                      Immutable, if set to true, ensures that data stored in the Secret cannot\n                      be updated (only object metadata can be modified).\n                      If not set to true, the field can be modified at any time.\n                      Defaulted to nil.\n                    type: boolean\n                  metadata:\n                    description: |-\n                      Standard object's metadata.\n                      More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata\n                    nullable: true\n                    properties:\n                      annotations:\n                        additionalProperties:\n                          type: string\n                        type: object\n                      finalizers:\n                        items:\n                          type: string\n                        type: array\n                      labels:\n                        additionalProperties:\n                          type: string\n                        type: object\n                      name:\n                        type: string\n                      namespace:\n                        type: string\n                    type: object\n                    x-kubernetes-preserve-unknown-fields: true\n                  type:\n                    description: Used to facilitate programmatic handling of secret\n                      data.\n                    type: string\n                type: object\n            required:\n            - encryptedData\n            type: object\n          status:\n            description: SealedSecretStatus is the most recently observed status of\n              the SealedSecret.\n            properties:\n              conditions:\n                description: Represents the latest available observations of a sealed\n                  secret's current state.\n                items:\n                  description: SealedSecretCondition describes the state of a sealed\n                    secret at a certain point.\n                  properties:\n                    lastTransitionTime:\n                      description: Last time the condition transitioned from one status\n                        to another.\n                      format: date-time\n                      type: string\n                    lastUpdateTime:\n                      description: The last time this condition was updated.\n                      format: date-time\n                      type: string\n                    message:\n                      description: A human readable message indicating details about\n                        the transition.\n                      type: string\n                    reason:\n                      description: The reason for the condition's last transition.\n                      type: string\n                    status:\n                      description: |-\n                        Status of the condition for a sealed secret.\n                        Valid values for \"Synced\": \"True\", \"False\", or \"Unknown\".\n                      type: string\n                    type:\n                      description: |-\n                        Type of condition for a sealed secret.\n                        Valid value: \"Synced\"\n                      type: string\n                  required:\n                  - status\n                  - type\n                  type: object\n                type: array\n              observedGeneration:\n                description: ObservedGeneration reflects the generation most recently\n                  observed by the sealed-secrets controller.\n                format: int64\n                type: integer\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "helm/sealed-secrets/dashboards/sealed-secrets-controller.json",
    "content": "{\n    \"annotations\": {\n        \"list\": [\n            {\n                \"builtIn\": 1,\n                \"datasource\": \"-- Grafana --\",\n                \"enable\": true,\n                \"hide\": true,\n                \"iconColor\": \"rgba(0, 211, 255, 1)\",\n                \"name\": \"Annotations & Alerts\",\n                \"type\": \"dashboard\"\n            }\n        ]\n    },\n    \"description\": \"Sealed Secrets Controller\",\n    \"editable\": true,\n    \"gnetId\": null,\n    \"graphTooltip\": 0,\n    \"id\": 3,\n    \"iteration\": 1585599163503,\n    \"links\": [\n        {\n            \"icon\": \"external link\",\n            \"tags\": [],\n            \"title\": \"GitHub\",\n            \"tooltip\": \"View Project on GitHub\",\n            \"type\": \"link\",\n            \"url\": \"https://github.com/bitnami-labs/sealed-secrets\"\n        }\n    ],\n    \"panels\": [\n        {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": \"$datasource\",\n            \"description\": \"Rate of requests to unseal a SealedSecret.\\n\\nThis can include non-obvious operations such as deleting a SealedSecret.\",\n            \"fill\": 1,\n            \"fillGradient\": 0,\n            \"gridPos\": {\n                \"h\": 9,\n                \"w\": 12,\n                \"x\": 0,\n                \"y\": 0\n            },\n            \"hiddenSeries\": false,\n            \"id\": 2,\n            \"legend\": {\n                \"avg\": true,\n                \"current\": false,\n                \"max\": true,\n                \"min\": true,\n                \"show\": true,\n                \"total\": false,\n                \"values\": true\n            },\n            \"lines\": true,\n            \"linewidth\": 1,\n            \"links\": [],\n            \"nullPointMode\": \"null\",\n            \"options\": {\n                \"dataLinks\": []\n            },\n            \"percentage\": false,\n            \"pointradius\": 2,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n                {\n                    \"expr\": \"sum(rate(sealed_secrets_controller_unseal_requests_total{}[1m]))\",\n                    \"format\": \"time_series\",\n                    \"instant\": false,\n                    \"intervalFactor\": 1,\n                    \"legendFormat\": \"rps\",\n                    \"refId\": \"A\"\n                }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"Unseal Request Rate/s\",\n            \"tooltip\": {\n                \"shared\": true,\n                \"sort\": 0,\n                \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n                \"buckets\": null,\n                \"mode\": \"time\",\n                \"name\": null,\n                \"show\": true,\n                \"values\": []\n            },\n            \"yaxes\": [\n                {\n                    \"format\": \"short\",\n                    \"label\": null,\n                    \"logBase\": 1,\n                    \"max\": null,\n                    \"min\": null,\n                    \"show\": true\n                },\n                {\n                    \"format\": \"short\",\n                    \"label\": null,\n                    \"logBase\": 1,\n                    \"max\": null,\n                    \"min\": null,\n                    \"show\": true\n                }\n            ],\n            \"yaxis\": {\n                \"align\": false,\n                \"alignLevel\": null\n            }\n        },\n        {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": \"$datasource\",\n            \"description\": \"Rate of errors when unsealing a SealedSecret. \\n\\nReason for error included as label value, eg:\\n- unseal = cryptography issue (key/namespace) or RBAC\\n- unmanaged = destination Secret wasn't created by SealedSecrets\\n- update = potentially RBAC\\n- status = potentially RBAC\\n- fetch = potentially RBAC\\n\",\n            \"fill\": 1,\n            \"fillGradient\": 0,\n            \"gridPos\": {\n                \"h\": 9,\n                \"w\": 12,\n                \"x\": 12,\n                \"y\": 0\n            },\n            \"hiddenSeries\": false,\n            \"id\": 3,\n            \"legend\": {\n                \"avg\": false,\n                \"current\": false,\n                \"hideEmpty\": false,\n                \"hideZero\": false,\n                \"max\": false,\n                \"min\": false,\n                \"show\": true,\n                \"total\": false,\n                \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 1,\n            \"links\": [],\n            \"nullPointMode\": \"null as zero\",\n            \"options\": {\n                \"dataLinks\": []\n            },\n            \"percentage\": false,\n            \"pointradius\": 2,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n                {\n                    \"expr\": \"sum(rate(sealed_secrets_controller_unseal_errors_total{pod=~\\\"$pod\\\"}[1m])) by (reason)\",\n                    \"format\": \"time_series\",\n                    \"intervalFactor\": 1,\n                    \"legendFormat\": \"{{ reason }}\",\n                    \"refId\": \"A\"\n                }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"Unseal Error Rate/s\",\n            \"tooltip\": {\n                \"shared\": true,\n                \"sort\": 0,\n                \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n                \"buckets\": null,\n                \"mode\": \"time\",\n                \"name\": null,\n                \"show\": true,\n                \"values\": []\n            },\n            \"yaxes\": [\n                {\n                    \"format\": \"short\",\n                    \"label\": null,\n                    \"logBase\": 1,\n                    \"max\": null,\n                    \"min\": null,\n                    \"show\": true\n                },\n                {\n                    \"format\": \"short\",\n                    \"label\": null,\n                    \"logBase\": 1,\n                    \"max\": null,\n                    \"min\": null,\n                    \"show\": true\n                }\n            ],\n            \"yaxis\": {\n                \"align\": false,\n                \"alignLevel\": null\n            }\n        }\n    ],\n    \"refresh\": false,\n    \"schemaVersion\": 22,\n    \"style\": \"dark\",\n    \"tags\": [],\n    \"templating\": {\n        \"list\": [\n            {\n                \"current\": {\n                    \"text\": \"prometheus\",\n                    \"value\": \"prometheus\"\n                },\n                \"hide\": 0,\n                \"includeAll\": false,\n                \"label\": null,\n                \"multi\": false,\n                \"name\": \"datasource\",\n                \"options\": [],\n                \"query\": \"prometheus\",\n                \"refresh\": 1,\n                \"regex\": \"\",\n                \"skipUrlSync\": false,\n                \"type\": \"datasource\"\n            },\n            {\n                \"allValue\": null,\n                \"current\": {\n                    \"selected\": false,\n                    \"text\": \"All\",\n                    \"value\": \"$__all\"\n                },\n                \"datasource\": \"$datasource\",\n                \"definition\": \"label_values(kube_pod_info, pod)\",\n                \"hide\": 0,\n                \"includeAll\": true,\n                \"label\": null,\n                \"multi\": false,\n                \"name\": \"pod\",\n                \"options\": [],\n                \"query\": \"label_values(kube_pod_info, pod)\",\n                \"refresh\": 1,\n                \"regex\": \"/^sealed-secrets-controller.*$/\",\n                \"skipUrlSync\": false,\n                \"sort\": 0,\n                \"tagValuesQuery\": \"\",\n                \"tags\": [],\n                \"tagsQuery\": \"\",\n                \"type\": \"query\",\n                \"useTags\": false\n            }\n        ]\n    },\n    \"time\": {\n        \"from\": \"now-1h\",\n        \"to\": \"now\"\n    },\n    \"timepicker\": {\n        \"refresh_intervals\": [\n            \"5s\",\n            \"10s\",\n            \"30s\",\n            \"1m\",\n            \"5m\",\n            \"15m\",\n            \"30m\",\n            \"1h\",\n            \"2h\",\n            \"1d\"\n        ],\n        \"time_options\": [\n            \"5m\",\n            \"15m\",\n            \"1h\",\n            \"6h\",\n            \"12h\",\n            \"24h\",\n            \"2d\",\n            \"7d\",\n            \"30d\"\n        ]\n    },\n    \"timezone\": \"\",\n    \"title\": \"Sealed Secrets Controller\",\n    \"uid\": \"UuEtZCVWz\",\n    \"version\": 2\n}\n"
  },
  {
    "path": "helm/sealed-secrets/templates/NOTES.txt",
    "content": "{{ if .Values.createController -}}\n\n** Please be patient while the chart is being deployed **\n\nYou should now be able to create sealed secrets.\n\n1. Install the client-side tool (kubeseal) as explained in the docs below:\n\n    https://github.com/bitnami-labs/sealed-secrets#installation-from-source\n\n2. Create a sealed secret file running the command below:\n\n    kubectl create secret generic secret-name --dry-run=client --from-literal=foo=bar -o [json|yaml] | \\\n    kubeseal \\\n      --controller-name={{ include \"sealed-secrets.fullname\" . }} \\\n      --controller-namespace={{ include \"sealed-secrets.namespace\" . }} \\\n      --format yaml > mysealedsecret.[json|yaml]\n\nThe file mysealedsecret.[json|yaml] is a commitable file.\n\nIf you would rather not need access to the cluster to generate the sealed secret you can run:\n\n    kubeseal \\\n      --controller-name={{ include \"sealed-secrets.fullname\" . }} \\\n      --controller-namespace={{ include \"sealed-secrets.namespace\" . }} \\\n      --fetch-cert > mycert.pem\n\nto retrieve the public cert used for encryption and store it locally. You can then run 'kubeseal --cert mycert.pem' instead to use the local cert e.g.\n\n    kubectl create secret generic secret-name --dry-run=client --from-literal=foo=bar -o [json|yaml] | \\\n    kubeseal \\\n      --controller-name={{ include \"sealed-secrets.fullname\" . }} \\\n      --controller-namespace={{ include \"sealed-secrets.namespace\" . }} \\\n      --format [json|yaml] --cert mycert.pem > mysealedsecret.[json|yaml]\n\n3. Apply the sealed secret\n\n    kubectl create -f mysealedsecret.[json|yaml]\n\nRunning 'kubectl get secret secret-name -o [json|yaml]' will show the decrypted secret that was generated from the sealed secret.\n\nBoth the SealedSecret and generated Secret must have the same name and namespace.\n{{- else }}\nSealed Secrets controller not installed, You need to install controller before\nsealed secrets can be created.\n{{- end }}\n"
  },
  {
    "path": "helm/sealed-secrets/templates/_helpers.tpl",
    "content": "{{/*\nExpand the name of the chart.\n*/}}\n{{- define \"sealed-secrets.name\" -}}\n{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix \"-\" -}}\n{{- end -}}\n\n{{/*\nCreate a default fully qualified app name.\nWe truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).\nIf release name contains chart name it will be used as a full name.\n*/}}\n{{- define \"sealed-secrets.fullname\" -}}\n{{- if .Values.fullnameOverride -}}\n{{- .Values.fullnameOverride | trunc 63 | trimSuffix \"-\" -}}\n{{- else -}}\n{{- $name := default .Chart.Name .Values.nameOverride -}}\n{{- if contains $name .Release.Name -}}\n{{- .Release.Name | trunc 63 | trimSuffix \"-\" -}}\n{{- else -}}\n{{- printf \"%s-%s\" .Release.Name $name | trunc 63 | trimSuffix \"-\" -}}\n{{- end -}}\n{{- end -}}\n{{- end -}}\n\n{{/*\nExpand to the namespace sealed-secrets installs into.\n*/}}\n{{- define \"sealed-secrets.namespace\" -}}\n{{- default .Release.Namespace .Values.namespace -}}\n{{- end -}}\n\n{{/*\nCreate chart name and version as used by the chart label.\n*/}}\n{{- define \"sealed-secrets.chart\" -}}\n{{- printf \"%s-%s\" .Chart.Name .Chart.Version | replace \"+\" \"_\" | trunc 63 | trimSuffix \"-\" -}}\n{{- end -}}\n\n{{/*\nCreate the name of the service account to use\n*/}}\n{{- define \"sealed-secrets.serviceAccountName\" -}}\n{{- if .Values.serviceAccount.create -}}\n    {{ default (include \"sealed-secrets.fullname\" .) .Values.serviceAccount.name }}\n{{- else -}}\n    {{ default \"default\" .Values.serviceAccount.name }}\n{{- end -}}\n{{- end -}}\n\n{{/*\nKubernetes standard labels\n*/}}\n{{- define \"sealed-secrets.labels\" -}}\napp.kubernetes.io/name: {{ include \"sealed-secrets.name\" . }}\nhelm.sh/chart: {{ include \"sealed-secrets.chart\" . }}\napp.kubernetes.io/instance: {{ .Release.Name }}\napp.kubernetes.io/managed-by: {{ .Release.Service }}\napp.kubernetes.io/version: {{ .Chart.AppVersion }}\napp.kubernetes.io/part-of: sealed-secrets\n{{- end -}}\n\n{{/*\nLabels to use on deploy.spec.selector.matchLabels and svc.spec.selector\n*/}}\n{{- define \"sealed-secrets.matchLabels\" -}}\napp.kubernetes.io/name: {{ include \"sealed-secrets.name\" . }}\napp.kubernetes.io/instance: {{ .Release.Name }}\n{{- end -}}\n\n{{/*\nReturn true if cert-manager required annotations for TLS signed certificates are set in the Ingress annotations\nRef: https://cert-manager.io/docs/usage/ingress/#supported-annotations\n*/}}\n{{- define \"sealed-secrets.ingress.certManagerRequest\" -}}\n{{ if or (hasKey . \"cert-manager.io/cluster-issuer\") (hasKey . \"cert-manager.io/issuer\") }}\n    {{- true -}}\n{{- end -}}\n{{- end -}}\n\n{{/*\nRenders a value that contains template.\nUsage:\n{{ include \"sealed-secrets.render\" ( dict \"value\" .Values.path.to.the.Value \"context\" $) }}\n*/}}\n{{- define \"sealed-secrets.render\" -}}\n    {{- if typeIs \"string\" .value }}\n        {{- tpl .value .context }}\n    {{- else }}\n        {{- tpl (.value | toYaml) .context }}\n    {{- end }}\n{{- end -}}\n\n{{/*\nReturn the target Kubernetes version\n*/}}\n{{- define \"sealed-secrets.kubeVersion\" -}}\n{{- if .Values.global }}\n    {{- if .Values.global.kubeVersion }}\n    {{- .Values.global.kubeVersion -}}\n    {{- else }}\n    {{- default .Capabilities.KubeVersion.Version .Values.kubeVersion -}}\n    {{- end -}}\n{{- else }}\n{{- default .Capabilities.KubeVersion.Version .Values.kubeVersion -}}\n{{- end -}}\n{{- end -}}\n\n{{/*\nReturn the appropriate apiVersion for deployment.\n*/}}\n{{- define \"sealed-secrets.deployment.apiVersion\" -}}\n{{- if semverCompare \"<1.14-0\" (include \"sealed-secrets.kubeVersion\" .) -}}\n{{- print \"extensions/v1beta1\" -}}\n{{- else -}}\n{{- print \"apps/v1\" -}}\n{{- end -}}\n{{- end -}}\n\n{{/*\nReturn the appropriate apiVersion for ingress.\n*/}}\n{{- define \"sealed-secrets.ingress.apiVersion\" -}}\n{{- if .Values.ingress -}}\n{{- if .Values.ingress.apiVersion -}}\n{{- .Values.ingress.apiVersion -}}\n{{- else if semverCompare \"<1.14-0\" (include \"sealed-secrets.kubeVersion\" .) -}}\n{{- print \"extensions/v1beta1\" -}}\n{{- else if semverCompare \"<1.19-0\" (include \"sealed-secrets.kubeVersion\" .) -}}\n{{- print \"networking.k8s.io/v1beta1\" -}}\n{{- else -}}\n{{- print \"networking.k8s.io/v1\" -}}\n{{- end }}\n{{- else if semverCompare \"<1.14-0\" (include \"sealed-secrets.kubeVersion\" .) -}}\n{{- print \"extensions/v1beta1\" -}}\n{{- else if semverCompare \"<1.19-0\" (include \"sealed-secrets.kubeVersion\" .) -}}\n{{- print \"networking.k8s.io/v1beta1\" -}}\n{{- else -}}\n{{- print \"networking.k8s.io/v1\" -}}\n{{- end -}}\n{{- end -}}\n\n{{/*\nReturn the appropriate apiVersion for networkpolicy.\n*/}}\n{{- define \"sealed-secrets.networkPolicy.apiVersion\" -}}\n{{- if semverCompare \"<1.7-0\" (include \"sealed-secrets.kubeVersion\" .) -}}\n{{- print \"extensions/v1beta1\" -}}\n{{- else -}}\n{{- print \"networking.k8s.io/v1\" -}}\n{{- end -}}\n{{- end -}}\n\nUsage:\n{{ include \"sealed-secrets.backend\" (dict \"serviceName\" \"backendName\" \"servicePort\" \"backendPort\" \"context\" $) }}\n\nParams:\n  - serviceName - String. Name of an existing service backend\n  - servicePort - String/Int. Port name (or number) of the service. It will be translated to different yaml depending if it is a string or an integer.\n  - context - Dict - Required. The context for the template evaluation.\n*/}}\n{{- define \"sealed-secrets.backend\" -}}\n{{- $apiVersion := (include \"sealed-secrets.ingress.apiVersion\" .context) -}}\n{{- if or (eq $apiVersion \"extensions/v1beta1\") (eq $apiVersion \"networking.k8s.io/v1beta1\") -}}\nserviceName: {{ .serviceName }}\nservicePort: {{ .servicePort }}\n{{- else -}}\nservice:\n  name: {{ .serviceName }}\n  port:\n    {{- if typeIs \"string\" .servicePort }}\n    name: {{ .servicePort }}\n    {{- else if or (typeIs \"int\" .servicePort) (typeIs \"float64\" .servicePort) }}\n    number: {{ .servicePort | int }}\n    {{- end }}\n{{- end -}}\n{{- end -}}\n\n{{/*\nPrint \"true\" if the API pathType field is supported\nUsage:\n{{ include \"sealed-secrets.supportsPathType\" . }}\n*/}}\n{{- define \"sealed-secrets.supportsPathType\" -}}\n{{- if (semverCompare \"<1.18-0\" (include \"sealed-secrets.kubeVersion\" .)) -}}\n{{- print \"false\" -}}\n{{- else -}}\n{{- print \"true\" -}}\n{{- end -}}\n{{- end -}}\n\n{{/*\nReturns true if the ingressClassname field is supported\nUsage:\n{{ include \"sealed-secrets.supportsIngressClassname\" . }}\n*/}}\n{{- define \"sealed-secrets.supportsIngressClassname\" -}}\n{{- if semverCompare \"<1.18-0\" (include \"sealed-secrets.kubeVersion\" .) -}}\n{{- print \"false\" -}}\n{{- else -}}\n{{- print \"true\" -}}\n{{- end -}}\n{{- end -}}\n"
  },
  {
    "path": "helm/sealed-secrets/templates/cluster-role-binding.yaml",
    "content": "{{ if and .Values.rbac.create (not .Values.rbac.namespacedRoles)}}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: {{ include \"sealed-secrets.fullname\" . }}\n  labels: {{- include \"sealed-secrets.labels\" . | nindent 4 }}\n    {{- if .Values.rbac.labels }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.rbac.labels \"context\" $) | nindent 4 }}\n    {{- end }}\n    {{- if .Values.commonLabels }}\n    {{- include \"sealed-secrets.render\" (dict \"value\" .Values.commonLabels \"context\" $) | nindent 4 }}\n    {{- end }}\n  annotations:\n    {{- if .Values.commonAnnotations }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.commonAnnotations \"context\" $ ) | nindent 4 }}\n    {{- end }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: {{ .Values.rbac.clusterRoleName }}\nsubjects:\n  - apiGroup: \"\"\n    kind: ServiceAccount\n    name: {{ include \"sealed-secrets.serviceAccountName\" . }}\n    namespace: {{ include \"sealed-secrets.namespace\" . }}\n{{ end }}\n"
  },
  {
    "path": "helm/sealed-secrets/templates/cluster-role.yaml",
    "content": "{{ if and (and .Values.rbac.create .Values.rbac.clusterRole) (not .Values.rbac.namespacedRoles) }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: {{ .Values.rbac.clusterRoleName }}\n  labels: {{- include \"sealed-secrets.labels\" . | nindent 4 }}\n    {{- if .Values.rbac.labels }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.rbac.labels \"context\" $) | nindent 4 }}\n    {{- end }}\n    {{- if .Values.commonLabels }}\n    {{- include \"sealed-secrets.render\" (dict \"value\" .Values.commonLabels \"context\" $) | nindent 4 }}\n    {{- end }}\n  annotations:\n    {{- if .Values.commonAnnotations }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.commonAnnotations \"context\" $ ) | nindent 4 }}\n    {{- end }}\nrules:\n  - apiGroups:\n      - bitnami.com\n    resources:\n      - sealedsecrets\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - bitnami.com\n    resources:\n      - sealedsecrets/status\n    verbs:\n      - update\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - create\n      - update\n      - delete\n      - watch\n  - apiGroups:\n      - \"\"\n    resources:\n      - events\n    verbs:\n      - create\n      - patch\n  {{- if .Values.additionalNamespaces }}\n  - apiGroups:\n      - \"\"\n    resources:\n      - namespaces\n    resourceNames:\n      {{- include \"sealed-secrets.render\" (dict \"value\" .Values.additionalNamespaces \"context\" $) | nindent 6 }}\n    verbs:\n      - get\n  {{- end }}\n{{ end }}\n"
  },
  {
    "path": "helm/sealed-secrets/templates/configmap-dashboards.yaml",
    "content": "{{- if .Values.metrics.dashboards.create }}\n{{- $namespace := .Values.metrics.dashboards.namespace | default $.Release.Namespace }}\n{{- range $path, $_ :=  .Files.Glob  \"dashboards/*.json\" }}\n{{- $filename := trimSuffix (ext $path) (base $path) }}\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: {{ printf \"%s-%s\" (include \"sealed-secrets.fullname\" $) $filename }}\n  namespace: {{ $namespace }}\n  labels: {{- include \"sealed-secrets.labels\" $ | nindent 4 }}\n    {{- if $.Values.metrics.dashboards.labels }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" $.Values.metrics.dashboards.labels \"context\" $) | nindent 4 }}\n    {{- end }}\n    {{- if $.Values.commonLabels }}\n    {{- include \"sealed-secrets.render\" (dict \"value\" $.Values.commonLabels \"context\" $) | nindent 4 }}\n    {{- end }}\n  annotations:\n    {{- if $.Values.metrics.dashboards.annotations }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" $.Values.metrics.dashboards.annotations \"context\" $) | nindent 4 }}\n    {{- end }}\n    {{- if $.Values.commonAnnotations }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" $.Values.commonAnnotations \"context\" $ ) | nindent 4 }}\n    {{- end }}\ndata:\n  {{ base $path }}: |-\n{{ $.Files.Get $path | indent 4 }}\n---\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "helm/sealed-secrets/templates/deployment.yaml",
    "content": "{{- if .Values.createController }}\napiVersion: {{ include \"sealed-secrets.deployment.apiVersion\" . }}\nkind: Deployment\nmetadata:\n  name: {{ include \"sealed-secrets.fullname\" . }}\n  namespace: {{ include \"sealed-secrets.namespace\" . }}\n  labels: {{- include \"sealed-secrets.labels\" . | nindent 4 }}\n    {{- if .Values.commonLabels }}\n    {{- include \"sealed-secrets.render\" (dict \"value\" .Values.commonLabels \"context\" $) | nindent 4 }}\n    {{- end }}\n  annotations:\n    {{- if .Values.commonAnnotations }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.commonAnnotations \"context\" $ ) | nindent 4 }}\n    {{- end }}\nspec:\n  replicas: 1\n  {{- if .Values.revisionHistoryLimit }}\n  revisionHistoryLimit: {{ .Values.revisionHistoryLimit }}\n  {{- end }}\n  selector:\n    matchLabels: {{- include \"sealed-secrets.matchLabels\" . | nindent 6 }}\n  template:\n    metadata:\n      {{- if .Values.podAnnotations }}\n      annotations: {{- toYaml .Values.podAnnotations | nindent 8 }}\n      {{- end }}\n      labels: {{- include \"sealed-secrets.matchLabels\" . | nindent 8 }}\n        {{- if .Values.podLabels }}\n        {{- toYaml .Values.podLabels | nindent 8 }}\n        {{- end }}\n    spec:\n      {{- if .Values.image.pullSecrets }}\n      imagePullSecrets:\n      {{- range .Values.image.pullSecrets }}\n        - name: {{ . }}\n      {{- end }}\n      {{- end }}\n      {{- if .Values.affinity }}\n      affinity: {{- toYaml .Values.affinity | nindent 8 }}\n      {{- end }}\n      {{- if .Values.nodeSelector }}\n      nodeSelector: {{- toYaml .Values.nodeSelector | nindent 8 }}\n      {{- end }}\n      {{- if .Values.tolerations }}\n      tolerations: {{- toYaml .Values.tolerations | nindent 8 }}\n      {{- end }}\n      {{- if .Values.priorityClassName }}\n      priorityClassName: {{ .Values.priorityClassName | quote }}\n      {{- end }}\n      {{- if .Values.runtimeClassName }}\n      runtimeClassName: {{ .Values.runtimeClassName | quote }}\n      {{- end }}\n      {{- if .Values.podSecurityContext.enabled }}\n      securityContext: {{- omit .Values.podSecurityContext \"enabled\" | toYaml | nindent 8 }}\n      {{- end }}\n      serviceAccountName: {{ include \"sealed-secrets.serviceAccountName\" . }}\n      {{- if .Values.hostNetwork }}\n      hostNetwork: true\n      {{- end }}\n      {{- if .Values.dnsPolicy }}\n      dnsPolicy: {{ .Values.dnsPolicy }}\n      {{- end }}\n      containers:\n        - name: controller\n          command:\n          {{- if .Values.command }}\n            {{- include \"sealed-secrets.render\" (dict \"value\" .Values.command \"context\" $) | nindent 12 }}\n          {{- else }}\n            - controller\n          {{- end }}\n          args:\n          {{- if .Values.args }}\n            {{- include \"sealed-secrets.render\" (dict \"value\" .Values.args \"context\" $) | nindent 12 }}\n          {{- else }}\n            {{- if .Values.updateStatus }}\n            - --update-status\n            {{- end }}\n            {{- if .Values.skipRecreate }}\n            - --skip-recreate\n            {{- end }}\n            {{- if ne (.Values.keyrenewperiod | toString) \"\" }}\n            - --key-renew-period\n            - {{ .Values.keyrenewperiod | quote }}\n            {{- end }}\n            {{- if .Values.keyttl }}\n            - --key-ttl\n            - {{ .Values.keyttl | quote }}\n            {{- end }}\n            {{- if .Values.keycutofftime }}\n            - --key-cutoff-time\n            - {{ .Values.keycutofftime | quote }}\n            {{- end }}\n            {{- if .Values.rateLimit }}\n            - --rate-limit\n            - {{ .Values.rateLimit | quote }}\n            {{- end }}\n            {{- if .Values.rateLimitBurst }}\n            - --rate-limit-burst\n            - {{ .Values.rateLimitBurst | quote }}\n            {{- end }}\n            - --key-prefix\n            - {{ .Values.secretName | quote }}\n            {{- if .Values.additionalNamespaces }}\n            - --additional-namespaces\n            - {{ join \",\" .Values.additionalNamespaces | quote }}\n            {{- end }}\n            {{- if $.Values.privateKeyAnnotations }}\n            {{- $privatekeyAnnotations := \"\"}}\n            {{- range $k, $v := $.Values.privateKeyAnnotations }}\n              {{- if not (and $v (kindIs \"string\" $v)) }}\n                {{ fail \"Annotation values have to be strings\"}}\n              {{- end }}\n              {{- $privatekeyAnnotations = printf \"%s=%s,%s\" $k $v $privatekeyAnnotations}}\n            {{- end }}\n            - --privatekey-annotations\n            - {{ trimSuffix \",\" $privatekeyAnnotations | quote }}\n            {{- end }}\n            {{- if $.Values.privateKeyLabels }}\n            {{- $privateKeyLabels := \"\"}}\n            {{- range $k, $v := $.Values.privateKeyLabels }}\n              {{- if not (and $v (kindIs \"string\" $v)) }}\n                {{ fail \"Label values have to be strings\"}}\n              {{- end }}\n              {{- $privateKeyLabels = printf \"%s=%s,%s\" $k $v $privateKeyLabels}}\n            {{- end }}\n            - --privatekey-labels\n            - {{ trimSuffix \",\" $privateKeyLabels | quote }}\n            {{- end }}\n            {{- if .Values.logInfoStdout }}\n            - --log-info-stdout\n            {{- end }}\n            {{- if .Values.logLevel }}\n            - --log-level\n            - {{ .Values.logLevel }}\n            {{- end }}\n            {{- if .Values.logFormat }}\n            - --log-format\n            - {{ .Values.logFormat }}\n            {{- end }}\n            {{- if .Values.containerPorts.http }}\n            - --listen-addr\n            - {{ printf \":%s\" (.Values.containerPorts.http | toString ) }}\n            {{- end }}\n            {{- if .Values.containerPorts.metrics }}\n            - --listen-metrics-addr\n            - {{ printf \":%s\" (.Values.containerPorts.metrics | toString) }}\n            {{- end }}\n            {{- if .Values.maxRetries }}\n            - --max-unseal-retries\n            - {{ .Values.maxRetries | quote }}\n            {{- end }}\n            {{- if .Values.watchForSecrets }}\n            - --watch-for-secrets\n            {{- end }}\n            {{- if .Values.kubeClientQPS }}\n            - --kubeclient-qps\n            - {{ .Values.kubeClientQPS | quote }}\n            {{- end }}\n            {{- if .Values.kubeClientBurst }}\n            - --kubeclient-burst\n            - {{ .Values.kubeClientBurst | quote }}\n            {{- end }}\n          {{- end }}\n          image: {{ printf \"%s/%s:%s\" .Values.image.registry .Values.image.repository .Values.image.tag }}\n          imagePullPolicy: {{ .Values.image.pullPolicy }}\n          env:\n            {{- if (.Values.resources.limits).cpu }}\n            - name: GOMAXPROCS\n              valueFrom:\n                resourceFieldRef:\n                  resource: limits.cpu\n                  divisor: \"1\"\n            {{- end }}\n            {{- if (.Values.resources.limits).memory }}\n            - name: GOMEMLIMIT\n              valueFrom:\n                resourceFieldRef:\n                  resource: limits.memory\n                  divisor: \"1\"\n            {{- end }}\n          ports:\n            - name: http\n              containerPort: {{ .Values.containerPorts.http | default \"8080\" }}\n              protocol: TCP\n              {{- if .Values.hostNetwork }}\n              hostPort: {{ .Values.containerPorts.http }}\n              {{- else if .Values.hostPorts.http }}\n              hostPort: {{ .Values.hostPorts.http }}\n              {{- end }}\n            - name: metrics\n              containerPort: {{ .Values.containerPorts.metrics | default \"8081\" }}\n              protocol: TCP\n              {{- if .Values.hostNetwork }}\n              hostPort: {{ .Values.containerPorts.metrics }}\n              {{- else if .Values.hostPorts.metrics }}\n              hostPort: {{ .Values.hostPorts.metrics }}\n              {{- end }}\n          {{- if .Values.startupProbe.enabled }}\n          startupProbe: {{- include \"sealed-secrets.render\" (dict \"value\" (omit .Values.startupProbe \"enabled\") \"context\" $) | nindent 12 }}\n            tcpSocket:\n              port: http\n          {{- else if .Values.customStartupProbe }}\n          startupProbe: {{- include \"sealed-secrets.render\" (dict \"value\" .Values.customStartupProbe \"context\" $) | nindent 12 }}\n          {{- end }}\n          {{- if .Values.livenessProbe.enabled }}\n          livenessProbe: {{- include \"sealed-secrets.render\" (dict \"value\" (omit .Values.livenessProbe \"enabled\") \"context\" $) | nindent 12 }}\n            httpGet:\n              path: /healthz\n              port: http\n          {{- else if .Values.customLivenessProbe }}\n          livenessProbe: {{- include \"sealed-secrets.render\" (dict \"value\" .Values.customLivenessProbe \"context\" $) | nindent 12 }}\n          {{- end }}\n          {{- if .Values.readinessProbe.enabled }}\n          readinessProbe: {{- include \"sealed-secrets.render\" (dict \"value\" (omit .Values.readinessProbe \"enabled\") \"context\" $) | nindent 12 }}\n            httpGet:\n              path: /healthz\n              port: http\n          {{- else if .Values.customReadinessProbe }}\n          readinessProbe: {{- include \"sealed-secrets.render\" (dict \"value\" .Values.customReadinessProbe \"context\" $) | nindent 12 }}\n          {{- end }}\n          {{- if .Values.resources }}\n          resources: {{- toYaml .Values.resources | nindent 12 }}\n          {{- end }}\n          {{- if .Values.containerSecurityContext.enabled }}\n          securityContext: {{- omit .Values.containerSecurityContext \"enabled\" | toYaml | nindent 12 }}\n          {{- end }}\n          volumeMounts:\n            {{- if .Values.additionalVolumeMounts }}\n              {{- toYaml .Values.additionalVolumeMounts | nindent 12 }}\n            {{- end }}\n            - mountPath: /tmp\n              name: tmp\n      volumes:\n      {{- if .Values.additionalVolumes }}\n        {{- toYaml .Values.additionalVolumes | nindent 8 }}\n      {{- end }}\n        - name: tmp\n          emptyDir: {}\n{{- end }}\n"
  },
  {
    "path": "helm/sealed-secrets/templates/extra-list.yaml",
    "content": "{{- range .Values.extraDeploy }}\n---\n{{ include \"sealed-secrets.render\" (dict \"value\" . \"context\" $) }}\n{{- end }}\n"
  },
  {
    "path": "helm/sealed-secrets/templates/ingress.yaml",
    "content": "{{- if and .Values.createController .Values.ingress.enabled }}\napiVersion: {{ include \"sealed-secrets.ingress.apiVersion\" . }}\nkind: Ingress\nmetadata:\n  name: {{ include \"sealed-secrets.fullname\" . }}\n  namespace: {{ include \"sealed-secrets.namespace\" . }}\n  labels: {{- include \"sealed-secrets.labels\" . | nindent 4 }}\n    {{- if .Values.commonLabels }}\n    {{- include \"sealed-secrets.render\" (dict \"value\" .Values.commonLabels \"context\" $) | nindent 4 }}\n    {{- end }}\n  annotations:\n    {{- if .Values.ingress.annotations }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.ingress.annotations \"context\" $) | nindent 4 }}\n    {{- end }}\n    {{- if .Values.commonAnnotations }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.commonAnnotations \"context\" $ ) | nindent 4 }}\n    {{- end }}\nspec:\n  {{- if and .Values.ingress.ingressClassName (eq \"true\" (include \"sealed-secrets.supportsIngressClassname\" .)) }}\n  ingressClassName: {{ .Values.ingress.ingressClassName | quote }}\n  {{- end }}\n  rules:\n    {{- if .Values.ingress.hostname }}\n    - host: {{ .Values.ingress.hostname }}\n      http:\n        paths:\n          {{- if .Values.ingress.extraPaths }}\n          {{- toYaml .Values.ingress.extraPaths | nindent 10 }}\n          {{- end }}\n          - path: {{ .Values.ingress.path }}\n            {{- if eq \"true\" (include \"sealed-secrets.supportsPathType\" .) }}\n            pathType: {{ .Values.ingress.pathType }}\n            {{- end }}\n            backend: {{- include \"sealed-secrets.backend\" (dict \"serviceName\" (include \"sealed-secrets.fullname\" .) \"servicePort\" \"http\" \"context\" $)  | nindent 14 }}\n    {{- end }}\n    {{- range .Values.ingress.extraHosts }}\n    - host: {{ .name | quote }}\n      http:\n        paths:\n          - path: {{ default \"/\" .path }}\n            {{- if eq \"true\" (include \"sealed-secrets.supportsPathType\" $) }}\n            pathType: {{ default \"ImplementationSpecific\" .pathType }}\n            {{- end }}\n            backend: {{- include \"sealed-secrets.backend\" (dict \"serviceName\" (include \"sealed-secrets.fullname\" $) \"servicePort\" \"http\" \"context\" $) | nindent 14 }}\n    {{- end }}\n  {{- if or (and .Values.ingress.tls (or (include \"sealed-secrets.ingress.certManagerRequest\" .Values.ingress.annotations) .Values.ingress.selfSigned)) .Values.ingress.extraTls }}\n  tls:\n    {{- if and .Values.ingress.tls (or (include \"sealed-secrets.ingress.certManagerRequest\" .Values.ingress.annotations) .Values.ingress.selfSigned) }}\n    - hosts:\n        - {{ .Values.ingress.hostname | quote }}\n      secretName: {{ printf \"%s-tls\" .Values.ingress.hostname }}\n    {{- end }}\n    {{- if .Values.ingress.extraTls }}\n    {{- include \"sealed-secrets.render\" (dict \"value\" .Values.ingress.extraTls \"context\" $) | nindent 4 }}\n    {{- end }}\n  {{- end }}\n{{- end }}\n"
  },
  {
    "path": "helm/sealed-secrets/templates/networkpolicy.yaml",
    "content": "{{- if .Values.networkPolicy.enabled }}\napiVersion: {{ include \"sealed-secrets.networkPolicy.apiVersion\" . }}\nkind: NetworkPolicy\nmetadata:\n  name: {{ include \"sealed-secrets.fullname\" . }}\n  namespace: {{ include \"sealed-secrets.namespace\" . }}\n  labels: {{- include \"sealed-secrets.labels\" . | nindent 4 }}\n    {{- if .Values.commonLabels }}\n    {{- include \"sealed-secrets.render\" (dict \"value\" .Values.commonLabels \"context\" $) | nindent 4 }}\n    {{- end }}\n  annotations:\n    {{- if .Values.commonAnnotations }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.commonAnnotations \"context\" $ ) | nindent 4 }}\n    {{- end }}\nspec:\n  podSelector:\n    matchLabels: {{- include \"sealed-secrets.matchLabels\" . | nindent 6 }}\n  ingress:\n    - ports:\n      - port: {{ .Values.service.port }}\n      - port: {{ .Values.metrics.service.port }}\n  {{- if .Values.networkPolicy.egress.enabled }}\n  egress:\n    - to:\n      {{- if not .Values.networkPolicy.egress.kubeapiCidr }}\n      {{- $kubernetesEndpoint := lookup \"v1\" \"Endpoints\" \"default\" \"kubernetes\" }}\n      {{- if $kubernetesEndpoint }}\n        {{- range $kubernetesAddress := (first $kubernetesEndpoint.subsets).addresses }}\n      - ipBlock:\n          cidr: {{ $kubernetesAddress.ip }}/32\n        {{- end}}      \n      ports:\n        {{- range $kubernetesPort := (first $kubernetesEndpoint.subsets).ports }}\n        - protocol: {{ $kubernetesPort.protocol }}\n          port: {{ $kubernetesPort.port }}\n        {{- end }}\n      {{- end}}\n      {{- else }}\n      - ipBlock:\n          cidr: {{ .Values.networkPolicy.egress.kubeapiCidr }}\n      ports:\n        - protocol: TCP\n          port: {{ .Values.networkPolicy.egress.kubeapiPort }}\n      {{- end }}\n  {{- end }}\n{{- end }}\n"
  },
  {
    "path": "helm/sealed-secrets/templates/pdb.yaml",
    "content": "{{- if .Values.pdb.create }}\nkind: PodDisruptionBudget\napiVersion: policy/v1\nmetadata:\n  name: {{ include \"sealed-secrets.fullname\" . }}\n  namespace: {{ include \"sealed-secrets.namespace\" . }}\n  labels: {{- include \"sealed-secrets.labels\" . | nindent 4 }}\n    {{- if .Values.commonLabels }}\n    {{- include \"sealed-secrets.render\" (dict \"value\" .Values.commonLabels \"context\" $) | nindent 4 }}\n    {{- end }}\n  annotations:\n    {{- if .Values.commonAnnotations }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.commonAnnotations \"context\" $ ) | nindent 4 }}\n    {{- end }}\nspec:\n  {{- if regexMatch \"64$\" (typeOf .Values.pdb.minAvailable) }}\n  minAvailable: {{ .Values.pdb.minAvailable }}\n  {{- end }}\n  {{- if regexMatch \"64$\" (typeOf .Values.pdb.maxUnavailable) }}\n  maxUnavailable: {{ .Values.pdb.maxUnavailable }}\n  {{- end }}\n  selector:\n    matchLabels: {{- include \"sealed-secrets.matchLabels\" . | nindent 6 }}\n{{- end }}\n"
  },
  {
    "path": "helm/sealed-secrets/templates/psp-clusterrole.yaml",
    "content": "{{- if .Values.rbac.pspEnabled }}\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: {{ printf \"%s-psp\" (include \"sealed-secrets.fullname\" .) }}\n  labels: {{- include \"sealed-secrets.labels\" . | nindent 4 }}\n    {{- if .Values.rbac.labels }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.rbac.labels \"context\" $) | nindent 4 }}\n    {{- end }}\n    {{- if .Values.commonLabels }}\n    {{- include \"sealed-secrets.render\" (dict \"value\" .Values.commonLabels \"context\" $) | nindent 4 }}\n    {{- end }}\n  annotations:\n    {{- if .Values.commonAnnotations }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.commonAnnotations \"context\" $ ) | nindent 4 }}\n    {{- end }}\nrules:\n  - apiGroups: ['extensions']\n    resources: ['podsecuritypolicies']\n    verbs:     ['use']\n    resourceNames:\n      - {{ include \"sealed-secrets.fullname\" . }}\n{{- end }}\n"
  },
  {
    "path": "helm/sealed-secrets/templates/psp-clusterrolebinding.yaml",
    "content": "{{- if .Values.rbac.pspEnabled }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: {{ printf \"%s-psp\" (include \"sealed-secrets.fullname\" .) }}\n  labels: {{- include \"sealed-secrets.labels\" . | nindent 4 }}\n    {{- if .Values.rbac.labels }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.rbac.labels \"context\" $) | nindent 4 }}\n    {{- end }}\n    {{- if .Values.commonLabels }}\n    {{- include \"sealed-secrets.render\" (dict \"value\" .Values.commonLabels \"context\" $) | nindent 4 }}\n    {{- end }}\n  annotations:\n    {{- if .Values.commonAnnotations }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.commonAnnotations \"context\" $ ) | nindent 4 }}\n    {{- end }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: {{ printf \"%s-psp\" (include \"sealed-secrets.fullname\" .) }}\nsubjects:\n  - kind: ServiceAccount\n    name: {{ include \"sealed-secrets.serviceAccountName\" . }}\n    namespace: {{ include \"sealed-secrets.namespace\" . }}\n{{- end }}\n"
  },
  {
    "path": "helm/sealed-secrets/templates/psp.yaml",
    "content": "{{- if .Values.rbac.pspEnabled }}\napiVersion: policy/v1beta1\nkind: PodSecurityPolicy\nmetadata:\n  name: {{ include \"sealed-secrets.fullname\" . }}\n  labels: {{- include \"sealed-secrets.labels\" . | nindent 4 }}\n    {{- if .Values.commonLabels }}\n    {{- include \"sealed-secrets.render\" (dict \"value\" .Values.commonLabels \"context\" $) | nindent 4 }}\n    {{- end }}\n  annotations:\n    {{- if .Values.commonAnnotations }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.commonAnnotations \"context\" $ ) | nindent 4 }}\n    {{- end }}\nspec:\n  privileged: false\n  allowPrivilegeEscalation: false\n  allowedCapabilities: []\n  volumes:\n    - 'configMap'\n    - 'emptyDir'\n    - 'projected'\n    - 'secret'\n    - 'downwardAPI'\n    - 'persistentVolumeClaim'\n  {{- if not .Values.hostNetwork }}\n  hostNetwork: false\n  {{- end }}\n  hostIPC: false\n  hostPID: false\n  runAsUser:\n    rule: 'RunAsAny'\n  seLinux:\n    rule: 'RunAsAny'\n  supplementalGroups:\n    rule: 'RunAsAny'\n  fsGroup:\n    rule: 'RunAsAny'\n{{- end }}\n"
  },
  {
    "path": "helm/sealed-secrets/templates/role-binding.yaml",
    "content": "{{ if .Values.rbac.create }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: {{ printf \"%s-key-admin\" (include \"sealed-secrets.fullname\" .) }}\n  namespace: {{ include \"sealed-secrets.namespace\" . }}\n  labels: {{- include \"sealed-secrets.labels\" . | nindent 4 }}\n    {{- if .Values.rbac.labels }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.rbac.labels \"context\" $) | nindent 4 }}\n    {{- end }}\n    {{- if .Values.commonLabels }}\n    {{- include \"sealed-secrets.render\" (dict \"value\" .Values.commonLabels \"context\" $) | nindent 4 }}\n    {{- end }}\n  annotations:\n    {{- if .Values.commonAnnotations }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.commonAnnotations \"context\" $ ) | nindent 4 }}\n    {{- end }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: {{ printf \"%s-key-admin\" (include \"sealed-secrets.fullname\" .) }}\nsubjects:\n  - apiGroup: \"\"\n    kind: ServiceAccount\n    name: {{ include \"sealed-secrets.serviceAccountName\" . }}\n    namespace: {{ include \"sealed-secrets.namespace\" . }}\n---\n{{ end }}\n{{ if and (and .Values.rbac.create .Values.rbac.serviceProxier.create) .Values.rbac.serviceProxier.bind }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: {{ printf \"%s-service-proxier\" (include \"sealed-secrets.fullname\" .) }}\n  namespace: {{ include \"sealed-secrets.namespace\" . }}\n  labels: {{- include \"sealed-secrets.labels\" . | nindent 4 }}\n    {{- if .Values.rbac.labels }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.rbac.labels \"context\" $) | nindent 4 }}\n    {{- end }}\n    {{- if .Values.commonLabels }}\n    {{- include \"sealed-secrets.render\" (dict \"value\" .Values.commonLabels \"context\" $) | nindent 4 }}\n    {{- end }}\n  annotations:\n    {{- if .Values.commonAnnotations }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.commonAnnotations \"context\" $ ) | nindent 4 }}\n    {{- end }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: {{ printf \"%s-service-proxier\" (include \"sealed-secrets.fullname\" .) }}\nsubjects:\n  {{- include \"sealed-secrets.render\" (dict \"value\" .Values.rbac.serviceProxier.subjects \"context\" $) | nindent 2 }}\n---\n{{ end }}\n{{ if and (and .Values.rbac.create .Values.rbac.namespacedRoles) (not $.Values.rbac.clusterRole) }}\n  {{- range $additionalNamespace := $.Values.additionalNamespaces }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: {{ include \"sealed-secrets.fullname\" $ }}\n  namespace: {{ $additionalNamespace }}\n  labels: {{- include \"sealed-secrets.labels\" $ | nindent 4 }}\n    {{- if $.Values.rbac.labels }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" $.Values.rbac.labels \"context\" $) | nindent 4 }}\n    {{- end }}\n  annotations:\n    {{- if $.Values.commonAnnotations }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" $.Values.commonAnnotations \"context\" $ ) | nindent 4 }}\n    {{- end }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: {{ $.Values.rbac.namespacedRolesName }}\nsubjects:\n  - apiGroup: \"\"\n    kind: ServiceAccount\n    name: {{ include \"sealed-secrets.serviceAccountName\" $ }}\n    namespace: {{ include \"sealed-secrets.namespace\" $ }}\n---\n  {{ end }}\n{{ end }}\n"
  },
  {
    "path": "helm/sealed-secrets/templates/role.yaml",
    "content": "{{ if .Values.rbac.create }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: {{ printf \"%s-key-admin\" (include \"sealed-secrets.fullname\" .) }}\n  namespace: {{ include \"sealed-secrets.namespace\" . }}\n  labels: {{- include \"sealed-secrets.labels\" . | nindent 4 }}\n    {{- if .Values.rbac.labels }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.rbac.labels \"context\" $) | nindent 4 }}\n    {{- end }}\n    {{- if .Values.commonLabels }}\n    {{- include \"sealed-secrets.render\" (dict \"value\" .Values.commonLabels \"context\" $) | nindent 4 }}\n    {{- end }}\n  annotations:\n    {{- if .Values.commonAnnotations }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.commonAnnotations \"context\" $ ) | nindent 4 }}\n    {{- end }}\nrules:\n  - apiGroups:\n      - \"\"\n    resourceNames:\n      - {{ .Values.secretName }}\n    resources:\n      - secrets\n    verbs:\n      - get\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - create\n      - list\n---\n{{- end }}\n{{- if and .Values.rbac.create .Values.rbac.serviceProxier.create }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: {{ printf \"%s-service-proxier\" (include \"sealed-secrets.fullname\" .) }}\n  namespace: {{ include \"sealed-secrets.namespace\" . }}\n  labels: {{- include \"sealed-secrets.labels\" . | nindent 4 }}\n    {{- if .Values.rbac.labels }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.rbac.labels \"context\" $) | nindent 4 }}\n    {{- end }}\n    {{- if .Values.commonLabels }}\n    {{- include \"sealed-secrets.render\" (dict \"value\" .Values.commonLabels \"context\" $) | nindent 4 }}\n    {{- end }}\nrules:\n  - apiGroups:\n      - \"\"\n    resourceNames:\n      - {{ include \"sealed-secrets.fullname\" . }}\n    resources:\n      - services\n    verbs:\n      - get\n  - apiGroups:\n      - \"\"\n    resourceNames:\n      - 'http:{{ include \"sealed-secrets.fullname\" . }}:'\n      - 'http:{{ include \"sealed-secrets.fullname\" . }}:http'\n      - {{ include \"sealed-secrets.fullname\" . }}\n    resources:\n      - services/proxy\n    verbs:\n      - create\n      - get\n---\n{{- end }}\n{{ if and (and .Values.rbac.create .Values.rbac.namespacedRoles) (not $.Values.rbac.clusterRole) }}\n  {{- range $additionalNamespace := $.Values.additionalNamespaces }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: {{ $.Values.rbac.namespacedRolesName }}\n  namespace: {{ $additionalNamespace }}\n  labels: {{- include \"sealed-secrets.labels\" $ | nindent 4 }}\n    {{- if $.Values.rbac.labels }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" $.Values.rbac.labels \"context\" $) | nindent 4 }}\n    {{- end }}\nrules:\n  - apiGroups:\n      - bitnami.com\n    resources:\n      - sealedsecrets\n    verbs:\n      - get\n      - list\n      - watch\n  - apiGroups:\n      - bitnami.com\n    resources:\n      - sealedsecrets/status\n    verbs:\n      - update\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n    verbs:\n      - get\n      - list\n      - create\n      - update\n      - delete\n      - watch\n  - apiGroups:\n      - \"\"\n    resources:\n      - events\n    verbs:\n      - create\n      - patch\n  - apiGroups:\n      - \"\"\n    resources:\n      - namespaces\n    resourceNames:\n      {{- include \"sealed-secrets.render\" (dict \"value\" $.Values.additionalNamespaces \"context\" $) | nindent 6 }}\n    verbs:\n      - get\n---\n  {{- end }}\n{{ end }}\n"
  },
  {
    "path": "helm/sealed-secrets/templates/service-account.yaml",
    "content": "{{ if .Values.serviceAccount.create }}\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{ include \"sealed-secrets.serviceAccountName\" . }}\n  namespace: {{ include \"sealed-secrets.namespace\" . }}\n  {{- if or (.Values.commonAnnotations) (.Values.serviceAccount.annotations) }}\n  annotations: \n    {{- if .Values.commonAnnotations }}\n    {{- toYaml .Values.commonAnnotations | nindent 4 }}\n    {{- end}}\n    {{- if .Values.serviceAccount.annotations }}\n    {{- toYaml .Values.serviceAccount.annotations | nindent 4 }}\n    {{- end}}\n  {{- end }}\n  labels: {{- include \"sealed-secrets.labels\" . | nindent 4 }}\n    {{- if .Values.serviceAccount.labels }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.serviceAccount.labels \"context\" $) | nindent 4 }}\n    {{- end }}\n    {{- if .Values.commonLabels }}\n    {{- include \"sealed-secrets.render\" (dict \"value\" .Values.commonLabels \"context\" $) | nindent 4 }}\n    {{- end }}\n{{ end }}\n"
  },
  {
    "path": "helm/sealed-secrets/templates/service.yaml",
    "content": "{{- if .Values.createController -}}\napiVersion: v1\nkind: Service\nmetadata:\n  name: {{ include \"sealed-secrets.fullname\" . }}\n  namespace: {{ include \"sealed-secrets.namespace\" . }}\n  {{- if or .Values.service.annotations .Values.commonAnnotations }}\n  annotations:\n    {{- if .Values.service.annotations }}\n    {{- include \"sealed-secrets.render\" (dict \"value\" .Values.service.annotations \"context\" $) | nindent 4 }}\n    {{- end }}\n    {{- if .Values.commonAnnotations }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.commonAnnotations \"context\" $ ) | nindent 4 }}\n    {{- end }}\n  {{- end }}\n  labels: {{- include \"sealed-secrets.labels\" . | nindent 4 }}\n    {{- if .Values.service.labels }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.service.labels \"context\" $) | nindent 4 }}\n    {{- end }}\n    {{- if .Values.commonLabels }}\n    {{- include \"sealed-secrets.render\" (dict \"value\" .Values.commonLabels \"context\" $) | nindent 4 }}\n    {{- end }}\nspec:\n  type: {{ .Values.service.type }}\n  {{- with .Values.service.loadBalancerClass }}\n  loadBalancerClass: {{ . }}\n  {{- end }}\n  ports:\n    - name: http\n      port: {{ .Values.service.port }}\n      targetPort: http\n      protocol: TCP\n      {{- if and (or (eq .Values.service.type \"NodePort\") (eq .Values.service.type \"LoadBalancer\")) (not (empty .Values.service.nodePort)) }}\n      nodePort: {{ .Values.service.nodePort }}\n      {{- else if eq .Values.service.type \"ClusterIP\" }}\n      nodePort: null\n      {{- end }}\n  selector: {{- include \"sealed-secrets.matchLabels\" . | nindent 4 }}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: {{ include \"sealed-secrets.fullname\" . }}-metrics\n  namespace: {{ include \"sealed-secrets.namespace\" . }}\n  {{- if or .Values.metrics.service.annotations .Values.commonAnnotations }}\n  annotations:\n    {{- if .Values.metrics.service.annotations }}\n    {{- include \"sealed-secrets.render\" (dict \"value\" .Values.metrics.service.annotations \"context\" $) | nindent 4 }}\n    {{- end }}\n    {{- if .Values.commonAnnotations }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.commonAnnotations \"context\" $ ) | nindent 4 }}\n    {{- end }}\n  {{- end }}\n  labels: {{- include \"sealed-secrets.labels\" . | nindent 4 }}\n    {{- if .Values.metrics.service.labels }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.metrics.service.labels \"context\" $) | nindent 4 }}\n    {{- end }}\n    {{- if .Values.commonLabels }}\n    {{- include \"sealed-secrets.render\" (dict \"value\" .Values.commonLabels \"context\" $) | nindent 4 }}\n    {{- end }}\n    app.kubernetes.io/component: metrics\nspec:\n  type: {{ .Values.metrics.service.type }}\n  {{- with .Values.metrics.service.loadBalancerClass }}\n  loadBalancerClass: {{ . }}\n  {{- end }}\n  ports:\n    - name: metrics\n      port: {{ .Values.metrics.service.port }}\n      targetPort: metrics\n      protocol: TCP\n      {{- if and (or (eq .Values.metrics.service.type \"NodePort\") (eq .Values.metrics.service.type \"LoadBalancer\")) (not (empty .Values.metrics.service.nodePort)) }}\n      nodePort: {{ .Values.metrics.service.nodePort }}\n      {{- else if eq .Values.metrics.service.type \"ClusterIP\" }}\n      nodePort: null\n      {{- end }}\n  selector: {{- include \"sealed-secrets.matchLabels\" . | nindent 4 }}\n{{- end }}\n"
  },
  {
    "path": "helm/sealed-secrets/templates/servicemonitor.yaml",
    "content": "{{- if .Values.metrics.serviceMonitor.enabled }}\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  name: {{ include \"sealed-secrets.fullname\" . }}\n  {{- if .Values.metrics.serviceMonitor.namespace }}\n  namespace: {{ .Values.metrics.serviceMonitor.namespace }}\n  {{- else }}\n  namespace: {{ include \"sealed-secrets.namespace\" . }}\n  {{- end }}\n  labels: {{- include \"sealed-secrets.labels\" . | nindent 4 }}\n    {{- if .Values.metrics.serviceMonitor.labels }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.metrics.serviceMonitor.labels \"context\" $) | nindent 4 }}\n    {{- end }}\n    {{- if .Values.commonLabels }}\n    {{- include \"sealed-secrets.render\" (dict \"value\" .Values.commonLabels \"context\" $) | nindent 4 }}\n    {{- end }}\n  annotations:\n    {{- if .Values.metrics.serviceMonitor.annotations }}\n    {{- include \"sealed-secrets.render\" (dict \"value\" .Values.metrics.serviceMonitor.annotations \"context\" $) | nindent 4 }}\n    {{- end }}\n    {{- if .Values.commonAnnotations }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.commonAnnotations \"context\" $ ) | nindent 4 }}\n    {{- end }}\nspec:\n  endpoints:\n    - port: metrics\n      {{- if .Values.metrics.serviceMonitor.honorLabels }}\n      honorLabels: {{ .Values.metrics.serviceMonitor.honorLabels }}\n      {{- end }}\n      {{- if .Values.metrics.serviceMonitor.interval }}\n      interval: {{ .Values.metrics.serviceMonitor.interval }}\n      {{- end }}\n      {{- if .Values.metrics.serviceMonitor.scrapeTimeout }}\n      scrapeTimeout: {{ .Values.metrics.serviceMonitor.scrapeTimeout }}\n      {{- end }}\n      {{- if .Values.metrics.serviceMonitor.metricRelabelings }}\n      metricRelabelings: {{ toYaml .Values.metrics.serviceMonitor.metricRelabelings | nindent 8 }}\n      {{- end }}\n      {{- if .Values.metrics.serviceMonitor.relabelings }}\n      relabelings: {{ toYaml .Values.metrics.serviceMonitor.relabelings | nindent 8 }}\n      {{- end }}\n  namespaceSelector:\n    matchNames:\n      - {{ include \"sealed-secrets.namespace\" . }}\n  selector:\n    matchLabels:\n      {{- include \"sealed-secrets.matchLabels\" . | nindent 6 }}\n      app.kubernetes.io/component: metrics\n{{- end }}\n"
  },
  {
    "path": "helm/sealed-secrets/templates/tls-secret.yaml",
    "content": "{{- if and .Values.createController .Values.ingress.enabled }}\n{{- if .Values.ingress.secrets }}\n{{- range .Values.ingress.secrets }}\napiVersion: v1\nkind: Secret\nmetadata:\n  name: {{ .name }}\n  namespace: {{ include \"sealed-secrets.namespace\" $ | quote }}\n  labels: {{- include \"sealed-secrets.labels\" $ | nindent 4 }}\n    {{- if .Values.commonLabels }}\n    {{- include \"sealed-secrets.render\" (dict \"value\" .Values.commonLabels \"context\" $) | nindent 4 }}\n    {{- end }}\n  annotations:\n    {{- if .Values.commonAnnotations }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.commonAnnotations \"context\" $ ) | nindent 4 }}\n    {{- end }}\ntype: kubernetes.io/tls\ndata:\n  tls.crt: {{ .certificate | b64enc }}\n  tls.key: {{ .key | b64enc }}\n---\n{{- end }}\n{{- end }}\n{{- if and .Values.ingress.tls .Values.ingress.selfSigned }}\n{{- $ca := genCA \"sealed-secrets-ca\" 365 }}\n{{- $cert := genSignedCert .Values.ingress.hostname nil (list .Values.ingress.hostname) 365 $ca }}\napiVersion: v1\nkind: Secret\nmetadata:\n  name: {{ printf \"%s-tls\" .Values.ingress.hostname }}\n  namespace: {{ include \"sealed-secrets.namespace\" . }}\n  labels: {{- include \"sealed-secrets.labels\" . | nindent 4 }}\n    {{- if .Values.commonLabels }}\n    {{- include \"sealed-secrets.render\" (dict \"value\" .Values.commonLabels \"context\" $) | nindent 4 }}\n    {{- end }}\n  annotations:\n    {{- if .Values.commonAnnotations }}\n    {{- include \"sealed-secrets.render\" ( dict \"value\" .Values.commonAnnotations \"context\" $ ) | nindent 4 }}\n    {{- end }}\ntype: kubernetes.io/tls\ndata:\n  tls.crt: {{ $cert.Cert | b64enc | quote }}\n  tls.key: {{ $cert.Key | b64enc | quote }}\n  ca.crt: {{ $ca.Cert | b64enc | quote }}\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "helm/sealed-secrets/values.yaml",
    "content": "## @section Common parameters\n\n## @param kubeVersion Override Kubernetes version\n##\nkubeVersion: \"\"\n## @param nameOverride String to partially override sealed-secrets.fullname\n##\nnameOverride: \"\"\n## @param fullnameOverride String to fully override sealed-secrets.fullname\n##\nfullnameOverride: \"\"\n## @param namespace Namespace where to deploy the Sealed Secrets controller\n##\nnamespace: \"\"\n\n## @param extraDeploy [array] Array of extra objects to deploy with the release\n##\nextraDeploy: []\n## @param commonAnnotations [object] Annotations to add to all deployed resources\n## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/\n##\ncommonAnnotations: {}\n\n## @param commonLabels [object] Labels to add to all deployed resources\n## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/\n## \ncommonLabels: {}\n\n## @section Sealed Secrets Parameters\n\n## Sealed Secrets image\n## ref: https://hub.docker.com/r/bitnami/sealed-secrets-controller/tags\n## @param image.registry Sealed Secrets image registry\n## @param image.repository Sealed Secrets image repository\n## @param image.tag Sealed Secrets image tag (immutable tags are recommended)\n## @param image.pullPolicy Sealed Secrets image pull policy\n## @param image.pullSecrets [array]  Sealed Secrets image pull secrets\n##\nimage:\n  registry: docker.io\n  repository: bitnami/sealed-secrets-controller\n  tag: 0.36.1\n  ## Specify a imagePullPolicy\n  ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent'\n  ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images\n  ##\n  pullPolicy: IfNotPresent\n  ## Optionally specify an array of imagePullSecrets.\n  ## Secrets must be manually created in the namespace.\n  ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/\n  ## e.g:\n  ## pullSecrets:\n  ##   - myRegistryKeySecretName\n  ##\n  pullSecrets: []\n## @param revisionHistoryLimit Number of old history to retain to allow rollback (If not set, default Kubernetes value is set to 10)\n## e.g:\nrevisionHistoryLimit: \"\"\n## @param createController Specifies whether the Sealed Secrets controller should be created\n##\ncreateController: true\n## @param secretName The name of an existing TLS secret containing the key used to encrypt secrets\n##\nsecretName: \"sealed-secrets-key\"\n## @param updateStatus Specifies whether the Sealed Secrets controller should update the status subresource\n##\nupdateStatus: true\n## @param skipRecreate Specifies whether the Sealed Secrets controller should skip recreating removed secrets\n## Setting it to true allows to optionally restore backward compatibility in low priviledge\n## environments when old versions of the controller did not require watch permissions on secrets\n## for secret re-creation.\n##\nskipRecreate: false\n## @param keyrenewperiod Specifies key renewal period. Default 30 days\n## e.g\n## keyrenewperiod: \"720h30m\"\n## To disable use \"0\", with quotes!\n##\nkeyrenewperiod: \"\"\n## @param keyttl Specifies the certificate validity duration. Default 10 years.\n## e.g for one year\n## keyttl: \"8760h00m00s\"\n##\nkeyttl: \"\"\n## @param keycutofftime Specifies a date at which the controller should generate a new certificate. Useful in early key renewal scenarios.\n## Takes a date formated according to RFC1123. Can be obtained with the 'date -R' command on a unix system.\n## e.g \n## keycutofftime: \"Mon, 14 Oct 2024 21:45:30 +0200\"\n##\nkeycutofftime: \"\"\n## @param rateLimit Number of allowed sustained request per second for verify endpoint\n##\nrateLimit: \"\"\n## @param rateLimitBurst Number of requests allowed to exceed the rate limit per second for verify endpoint\n##\nrateLimitBurst: \"\"\n## @param additionalNamespaces List of namespaces used to manage the Sealed Secrets\n##\nadditionalNamespaces: []\n## @param privateKeyAnnotations Map of annotations to be set on the sealing keypairs\n## \nprivateKeyAnnotations: {}\n## @param privateKeyLabels Map of labels to be set on the sealing keypairs\n## \nprivateKeyLabels: {}\n## @param logInfoStdout Specifies whether the Sealed Secrets controller will log info to stdout\n##\nlogInfoStdout: false\n## @param logLevel Specifies log level of controller (INFO,ERROR)\n##\nlogLevel: \"\"\n## @param logFormat Specifies log format (text,json)\n##\nlogFormat: \"\"\n## @param maxRetries Number of maximum retries\n##\nmaxRetries: \"\"\n## @param watchForSecrets Specifies whether the Sealed Secrets controller will watch for new secrets\n##\nwatchForSecrets: false\n## @param kubeClientQPS Kubeclient QPS (negative value disables ratelimiting)\n##\nkubeClientQPS: \"\"\n## @param kubeClientBurst Kubeclient Burst\n##\nkubeClientBurst: \"\"\n## @param command Override default container command\n##\ncommand: []\n## @param args Override default container args\n##\nargs: []\n## Configure extra options for Sealed Secret containers' liveness, readiness and startup probes\n## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#configure-probes\n## @param livenessProbe.enabled Enable livenessProbe on Sealed Secret containers\n## @param livenessProbe.initialDelaySeconds Initial delay seconds for livenessProbe\n## @param livenessProbe.periodSeconds Period seconds for livenessProbe\n## @param livenessProbe.timeoutSeconds Timeout seconds for livenessProbe\n## @param livenessProbe.failureThreshold Failure threshold for livenessProbe\n## @param livenessProbe.successThreshold Success threshold for livenessProbe\n##\nlivenessProbe:\n  enabled: true\n  initialDelaySeconds: 0\n  periodSeconds: 10\n  timeoutSeconds: 1\n  failureThreshold: 3\n  successThreshold: 1\n## @param readinessProbe.enabled Enable readinessProbe on Sealed Secret containers\n## @param readinessProbe.initialDelaySeconds Initial delay seconds for readinessProbe\n## @param readinessProbe.periodSeconds Period seconds for readinessProbe\n## @param readinessProbe.timeoutSeconds Timeout seconds for readinessProbe\n## @param readinessProbe.failureThreshold Failure threshold for readinessProbe\n## @param readinessProbe.successThreshold Success threshold for readinessProbe\n##\nreadinessProbe:\n  enabled: true\n  initialDelaySeconds: 0\n  periodSeconds: 10\n  timeoutSeconds: 1\n  failureThreshold: 3\n  successThreshold: 1\n## @param startupProbe.enabled Enable startupProbe on Sealed Secret containers\n## @param startupProbe.initialDelaySeconds Initial delay seconds for startupProbe\n## @param startupProbe.periodSeconds Period seconds for startupProbe\n## @param startupProbe.timeoutSeconds Timeout seconds for startupProbe\n## @param startupProbe.failureThreshold Failure threshold for startupProbe\n## @param startupProbe.successThreshold Success threshold for startupProbe\n##\nstartupProbe:\n  enabled: false\n  initialDelaySeconds: 0\n  periodSeconds: 10\n  timeoutSeconds: 1\n  failureThreshold: 3\n  successThreshold: 1\n## @param customLivenessProbe Custom livenessProbe that overrides the default one\n##\ncustomLivenessProbe: {}\n## @param customReadinessProbe Custom readinessProbe that overrides the default one\n##\ncustomReadinessProbe: {}\n## @param customStartupProbe Custom startupProbe that overrides the default one\n##\ncustomStartupProbe: {}\n## Sealed Secret resource requests and limits\n## ref: http://kubernetes.io/docs/user-guide/compute-resources/\n## @param resources.limits [object] The resources limits for the Sealed Secret containers\n## @param resources.requests [object] The requested resources for the Sealed Secret containers\n##\nresources:\n  limits: {}\n  requests: {}\n## Configure Pods Security Context\n## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod\n## @param podSecurityContext.enabled Enabled Sealed Secret pods' Security Context\n## @param podSecurityContext.fsGroup Set Sealed Secret pod's Security Context fsGroup\n##\npodSecurityContext:\n  enabled: true\n  fsGroup: 65534\n## Configure Container Security Context\n## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod\n## @param containerSecurityContext.enabled Enabled Sealed Secret containers' Security Context\n## @param containerSecurityContext.readOnlyRootFilesystem Whether the Sealed Secret container has a read-only root filesystem\n## @param containerSecurityContext.runAsNonRoot Indicates that the Sealed Secret container must run as a non-root user\n## @param containerSecurityContext.runAsUser Set Sealed Secret containers' Security Context runAsUser\n## @extra containerSecurityContext.capabilities Adds and removes POSIX capabilities from running containers (see `values.yaml`)\n## @skip  containerSecurityContext.capabilities.drop\n##\ncontainerSecurityContext:\n  enabled: true\n  readOnlyRootFilesystem: true\n  runAsNonRoot: true\n  runAsUser: 1001\n  capabilities:\n    drop:\n      - ALL\n\n## @param podLabels [object] Extra labels for Sealed Secret pods\n## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/\n##\npodLabels: {}\n## @param podAnnotations [object] Annotations for Sealed Secret pods\n## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/\n##\npodAnnotations: {}\n## @param priorityClassName Sealed Secret pods' priorityClassName\n##\npriorityClassName: \"\"\n## @param runtimeClassName Sealed Secret pods' runtimeClassName\n##\nruntimeClassName: \"\"\n## @param affinity [object] Affinity for Sealed Secret pods assignment\n## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity\n##\naffinity: {}\n## @param nodeSelector [object] Node labels for Sealed Secret pods assignment\n## ref: https://kubernetes.io/docs/user-guide/node-selection/\n##\nnodeSelector: {}\n## @param tolerations [array] Tolerations for Sealed Secret pods assignment\n## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/\n##\ntolerations: []\n## @param additionalVolumes [object] Extra Volumes for the Sealed Secrets Controller Deployment\n## ref: https://kubernetes.io/docs/concepts/storage/volumes/\n##\nadditionalVolumes: []\n## @param additionalVolumeMounts [object] Extra volumeMounts for the Sealed Secrets Controller container\n## ref: https://kubernetes.io/docs/concepts/storage/volumes/\n##\nadditionalVolumeMounts: []\n## @param hostNetwork Sealed Secrets pods' hostNetwork\nhostNetwork: false\n## Sealed Secrets controller ports to open\n## If hostNetwork true: the hostPort is set identical to the containerPort\n## @param containerPorts.http Controller HTTP Port on the Host and Container\n## @param containerPorts.metrics Metrics HTTP Port on the Host and Container\n##\ncontainerPorts:\n  http: 8080\n  metrics: 8081\n## Sealed Secrets controller ports to be exposed as hostPort\n## If hostNetwork is false, only the ports specified here will be exposed (or not if set to an empty string)\n## @param hostPorts.http Controller HTTP Port on the Host\n## @param hostPorts.metrics Metrics HTTP Port on the Host\n##\nhostPorts:\n  http: \"\"\n  metrics: \"\"\n\n## @param dnsPolicy Sealed Secrets pods' dnsPolicy\ndnsPolicy: \"\"\n## @section Traffic Exposure Parameters\n\n## Sealed Secret service parameters\n##\nservice:\n  ## @param service.type Sealed Secret service type\n  ##\n  type: ClusterIP\n  ## @param service.loadBalancerClass Sealed Secret service loadBalancerClass\n  ##\n  loadBalancerClass: \"\"\n  ## @param service.port Sealed Secret service HTTP port\n  ##\n  port: 8080\n  ## @param service.nodePort Node port for HTTP\n  ## Specify the nodePort value for the LoadBalancer and NodePort service types\n  ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport\n  ## NOTE: choose port between <30000-32767>\n  ##\n  nodePort: \"\"\n  ## @param service.annotations [object] Additional custom annotations for Sealed Secret service\n  ##\n  annotations: {}\n## Sealed Secret ingress parameters\n## ref: http://kubernetes.io/docs/user-guide/ingress/\n##\ningress:\n  ## @param ingress.enabled Enable ingress record generation for Sealed Secret\n  ##\n  enabled: false\n  ## @param ingress.pathType Ingress path type\n  ##\n  pathType: ImplementationSpecific\n  ## @param ingress.apiVersion Force Ingress API version (automatically detected if not set)\n  ##\n  apiVersion: \"\"\n  ## @param ingress.ingressClassName IngressClass that will be be used to implement the Ingress\n  ## This is supported in Kubernetes 1.18+ and required if you have more than one IngressClass marked as the default for your cluster.\n  ## ref: https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/\n  ##\n  ingressClassName: \"\"\n  ## @param ingress.hostname Default host for the ingress record\n  ##\n  hostname: sealed-secrets.local\n  ## @param ingress.path Default path for the ingress record\n  ##\n  path: /v1/cert.pem\n  ## @param ingress.annotations [object] Additional annotations for the Ingress resource. To enable certificate autogeneration, place here your cert-manager annotations.\n  ## Use this parameter to set the required annotations for cert-manager, see\n  ## ref: https://cert-manager.io/docs/usage/ingress/#supported-annotations\n  ## e.g:\n  ## annotations:\n  ##   kubernetes.io/ingress.class: nginx\n  ##   cert-manager.io/cluster-issuer: cluster-issuer-name\n  ##\n  annotations: {}\n  ## @param ingress.tls Enable TLS configuration for the host defined at `ingress.hostname` parameter\n  ## TLS certificates will be retrieved from a TLS secret with name: `{{- printf \"%s-tls\" .Values.ingress.hostname }}`\n  ## You can:\n  ##   - Use the `ingress.secrets` parameter to create this TLS secret\n  ##   - Relay on cert-manager to create it by setting the corresponding annotations\n  ##   - Relay on Helm to create self-signed certificates by setting `ingress.selfSigned=true`\n  ##\n  tls: false\n  ## @param ingress.selfSigned Create a TLS secret for this ingress record using self-signed certificates generated by Helm\n  ##\n  selfSigned: false\n  ## @param ingress.extraHosts [array] An array with additional hostname(s) to be covered with the ingress record\n  ## e.g:\n  ## extraHosts:\n  ##   - name: sealed-secrets.local\n  ##     path: /\n  ##\n  extraHosts: []\n  ## @param ingress.extraPaths [array] An array with additional arbitrary paths that may need to be added to the ingress under the main host\n  ## e.g:\n  ## extraPaths:\n  ## - path: /*\n  ##   backend:\n  ##     serviceName: ssl-redirect\n  ##     servicePort: use-annotation\n  ##\n  extraPaths: []\n  ## @param ingress.extraTls [array] TLS configuration for additional hostname(s) to be covered with this ingress record\n  ## ref: https://kubernetes.io/docs/concepts/services-networking/ingress/#tls\n  ## e.g:\n  ## extraTls:\n  ## - hosts:\n  ##     - sealed-secrets.local\n  ##   secretName: sealed-secrets.local-tls\n  ##\n  extraTls: []\n  ## @param ingress.secrets [array] Custom TLS certificates as secrets\n  ## NOTE: 'key' and 'certificate' are expected in PEM format\n  ## NOTE: 'name' should line up with a 'secretName' set further up\n  ## If it is not set and you're using cert-manager, this is unneeded, as it will create a secret for you with valid certificates\n  ## If it is not set and you're NOT using cert-manager either, self-signed certificates will be created valid for 365 days\n  ## It is also possible to create and manage the certificates outside of this helm chart\n  ## Please see README.md for more information\n  ## e.g:\n  ## secrets:\n  ##   - name: sealed-secrets.local-tls\n  ##     key: |-\n  ##       -----BEGIN RSA PRIVATE KEY-----\n  ##       ...\n  ##       -----END RSA PRIVATE KEY-----\n  ##     certificate: |-\n  ##       -----BEGIN CERTIFICATE-----\n  ##       ...\n  ##       -----END CERTIFICATE-----\n  ##\n  secrets: []\n## Network policies\n## Ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/\n##\nnetworkPolicy:\n  ## @param networkPolicy.enabled Specifies whether a NetworkPolicy should be created\n  ##\n  enabled: false\n  ## NetworkPolicy Egress configuration\n  ##\n  egress:\n    ## @param networkPolicy.egress.enabled Specifies wheter a egress is set in the NetworkPolicy\n    ##\n    enabled: false\n    ## @param networkPolicy.egress.kubeapiCidr Specifies the kubeapiCidr, which is the only egress allowed. If not set, kubeapiCidr will be found using Helm lookup\n    ##\n    kubeapiCidr: \"\"\n    ## @param networkPolicy.egress.kubeapiPort Specifies the kubeapiPort, which is the only egress allowed. If not set, kubeapiPort will be found using Helm lookup\n    ##\n    kubeapiPort: \"\"\n\n## @section Other Parameters\n\n## ServiceAccount configuration\n##\nserviceAccount:\n  ## @param serviceAccount.annotations [object] Annotations for Sealed Secret service account\n  ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/\n  ##\n  annotations: {}\n  ## @param serviceAccount.create Specifies whether a ServiceAccount should be created\n  ##\n  create: true\n  ## @param serviceAccount.labels Extra labels to be added to the ServiceAccount\n  ##\n  labels: {}\n  ## @param serviceAccount.name The name of the ServiceAccount to use.\n  ## If not set and create is true, a name is generated using the sealed-secrets.fullname template\n  ##\n  name: \"\"\n## RBAC configuration\n##\nrbac:\n  ## @param rbac.create Specifies whether RBAC resources should be created\n  ##\n  create: true\n  ## @param rbac.clusterRole Specifies whether the Cluster Role resource should be created\n  ##\n  clusterRole: true\n  ## @param rbac.clusterRoleName Specifies the name for the Cluster Role resource\n  ##\n  clusterRoleName: \"secrets-unsealer\"\n  ## @param rbac.namespacedRoles Specifies whether the namespaced Roles should be created (in each of the specified additionalNamespaces)\n  ##\n  namespacedRoles: false\n  ## @param rbac.namespacedRolesName Specifies the name for the namespaced Role resource\n  ##\n  namespacedRolesName: \"secrets-unsealer\"\n  ## @param rbac.labels Extra labels to be added to RBAC resources\n  ##\n  labels: {}\n  ## @param rbac.pspEnabled PodSecurityPolicy\n  ##\n  pspEnabled: false\n  ## \"Proxier\" RBAC Role configuration\n  ##\n  serviceProxier:\n    ## @param rbac.serviceProxier.create Specifies whether to create the \"proxier\" role, to allow external users to access the SealedSecret API\n    ##\n    create: true\n    ## @param rbac.serviceProxier.bind Specifies whether to create a RoleBinding for the \"proxier\" role\n    ##\n    bind: true\n    ## @param rbac.serviceProxier.subjects Specifies the RBAC subjects to grant the \"proxier\" role to, in the created RoleBinding\n    ## It is best to change this to something narrower, as the default binding gives `system:authenticated` access, which is very broad\n    ##\n    subjects: |\n      - apiGroup: rbac.authorization.k8s.io\n        kind: Group\n        name: system:authenticated\n\n## @section Metrics parameters\n\nmetrics:\n  ## Prometheus Operator ServiceMonitor configuration\n  ##\n  serviceMonitor:\n    ## @param metrics.serviceMonitor.enabled Specify if a ServiceMonitor will be deployed for Prometheus Operator\n    ##\n    enabled: false\n    ## @param metrics.serviceMonitor.namespace Namespace where Prometheus Operator is running in\n    ##\n    namespace: \"\"\n    ## @param metrics.serviceMonitor.labels Extra labels for the ServiceMonitor\n    ##\n    labels: {}\n    ## @param metrics.serviceMonitor.annotations Extra annotations for the ServiceMonitor\n    ##\n    annotations: {}\n    ## @param metrics.serviceMonitor.interval How frequently to scrape metrics\n    ## e.g:\n    ## interval: 10s\n    ##\n    interval: \"\"\n    ## @param metrics.serviceMonitor.scrapeTimeout Timeout after which the scrape is ended\n    ## e.g:\n    ## scrapeTimeout: 10s\n    ##\n    scrapeTimeout: \"\"\n    ## @param metrics.serviceMonitor.honorLabels Specify if ServiceMonitor endPoints will honor labels\n    ##\n    honorLabels: true\n    ## @param metrics.serviceMonitor.metricRelabelings [array] Specify additional relabeling of metrics\n    ##\n    metricRelabelings: []\n    ## @param metrics.serviceMonitor.relabelings [array] Specify general relabeling\n    ##\n    relabelings: []\n  ## Grafana dashboards configuration\n  ##\n  dashboards:\n    ## @param metrics.dashboards.create Specifies whether a ConfigMap with a Grafana dashboard configuration should be created\n    ## ref https://github.com/helm/charts/tree/master/stable/grafana#configuration\n    ##\n    create: false\n    ## @param metrics.dashboards.labels Extra labels to be added to the Grafana dashboard ConfigMap\n    ##\n    labels: {}\n    ## @param metrics.dashboards.annotations Annotations to be added to the Grafana dashboard ConfigMap\n    ##\n    annotations: {}\n    ## @param metrics.dashboards.namespace Namespace where Grafana dashboard ConfigMap is deployed\n    ##\n    namespace: \"\"\n\n  ## Sealed Secret Metrics service parameters\n  ##\n  service:\n    ## @param metrics.service.type Sealed Secret Metrics service type\n    ##\n    type: ClusterIP\n    ## @param metrics.service.loadBalancerClass Sealed Secret Metrics service loadBalancerClass\n    ##\n    loadBalancerClass: \"\"\n    ## @param metrics.service.port Sealed Secret service Metrics HTTP port\n    ##\n    port: 8081\n    ## @param metrics.service.nodePort Node port for HTTP\n    ## Specify the nodePort value for the LoadBalancer and NodePort service types\n    ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport\n    ## NOTE: choose port between <30000-32767>\n    ##\n    nodePort: \"\"\n    ## @param metrics.service.annotations [object] Additional custom annotations for Sealed Secret Metrics service\n    ##\n    annotations: {}\n\n## @section PodDisruptionBudget Parameters\n\npdb:\n  ## @param pdb.create Specifies whether a PodDisruptionBudget should be created\n  ##\n  create: false\n  ## @param pdb.minAvailable The minimum number of pods (non number to omit)\n  ##\n  minAvailable: 1\n  ## @param pdb.maxUnavailable The maximum number of unavailable pods (non number to omit)\n  ##\n  maxUnavailable: \"\"\n"
  },
  {
    "path": "integration/controller_test.go",
    "content": "//go:build integration\n// +build integration\n\npackage integration\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"sort\"\n\t\"time\"\n\n\t\"github.com/onsi/gomega/types\"\n\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/fields\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\tcorev1 \"k8s.io/client-go/kubernetes/typed/core/v1\"\n\tcertUtil \"k8s.io/client-go/util/cert\"\n\t\"k8s.io/client-go/util/keyutil\"\n\n\tssv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/apis/sealedsecrets/v1alpha1\"\n\tssclient \"github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned\"\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/crypto\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n)\n\nvar keySelector = fields.OneTermEqualSelector(\"sealedsecrets.bitnami.com/sealed-secrets-key\", \"active\").String()\n\nconst (\n\tTimeout         = 15 * time.Second\n\tPollingInterval = \"100ms\"\n)\n\nfunc getData(s *v1.Secret) map[string][]byte {\n\treturn s.Data\n}\n\nfunc getAnnotations(s *v1.Secret) map[string]string {\n\treturn s.ObjectMeta.Annotations\n}\n\nfunc getLabels(s *v1.Secret) map[string]string {\n\treturn s.ObjectMeta.Labels\n}\n\nfunc getStatus(ss *ssv1alpha1.SealedSecret) *ssv1alpha1.SealedSecretStatus {\n\treturn ss.Status\n}\n\nfunc getObservedGeneration(ss *ssv1alpha1.SealedSecret) int64 {\n\treturn ss.Status.ObservedGeneration\n}\n\n// get the first owner name assuming there is only one owner which is the sealed-secret object\nfunc getFirstOwnerName(s *v1.Secret) string {\n\treturn s.OwnerReferences[0].Name\n}\n\nfunc getNumberOfOwners(s *v1.Secret) int {\n\treturn len(s.OwnerReferences)\n}\n\nfunc getSecretType(s *v1.Secret) v1.SecretType {\n\treturn s.Type\n}\n\nfunc getSecretImmutable(s *v1.Secret) bool {\n\treturn *s.Immutable\n}\n\nfunc compareLastTimes(ss *ssv1alpha1.SealedSecret) bool {\n\tfor i := range ss.Status.Conditions {\n\t\tif ss.Status.Conditions[i].Type == ssv1alpha1.SealedSecretSynced {\n\t\t\treturn ss.Status.Conditions[i].LastTransitionTime == ss.Status.Conditions[i].LastUpdateTime\n\t\t}\n\t}\n\treturn false\n}\n\nfunc fetchKeys(ctx context.Context, c corev1.SecretsGetter) (map[string]*rsa.PrivateKey, []*x509.Certificate, error) {\n\tlist, err := c.Secrets(*controllerNs).List(ctx, metav1.ListOptions{\n\t\tLabelSelector: keySelector,\n\t})\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tif len(list.Items) == 0 {\n\t\treturn nil, nil, fmt.Errorf(\"found 0 keys\")\n\t}\n\n\tsort.Sort(ssv1alpha1.ByCreationTimestamp(list.Items))\n\tlatestKey := &list.Items[len(list.Items)-1]\n\n\tprivKey, err := keyutil.ParsePrivateKeyPEM(latestKey.Data[v1.TLSPrivateKeyKey])\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tcerts, err := certUtil.ParseCertsPEM(latestKey.Data[v1.TLSCertKey])\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tif len(certs) == 0 {\n\t\treturn nil, nil, fmt.Errorf(\"failed to read any certificates\")\n\t}\n\n\trsaPrivKey := privKey.(*rsa.PrivateKey)\n\tfp, err := crypto.PublicKeyFingerprint(&rsaPrivKey.PublicKey)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tprivKeys := map[string]*rsa.PrivateKey{fp: rsaPrivKey}\n\treturn privKeys, certs, nil\n}\n\nfunc containEventWithReason(matcher types.GomegaMatcher) types.GomegaMatcher {\n\treturn WithTransform(\n\t\tfunc(l *v1.EventList) []v1.Event { return l.Items },\n\t\tContainElement(WithTransform(\n\t\t\tfunc(e v1.Event) string { return e.Reason },\n\t\t\tmatcher,\n\t\t)),\n\t)\n}\n\nfunc containEventWithMessage(matcher types.GomegaMatcher) types.GomegaMatcher {\n\treturn WithTransform(\n\t\tfunc(l *v1.EventList) []v1.Event { return l.Items },\n\t\tContainElement(WithTransform(\n\t\t\tfunc(e v1.Event) string { return e.Message },\n\t\t\tmatcher,\n\t\t)),\n\t)\n}\n\nvar _ = Describe(\"create\", func() {\n\tvar c corev1.CoreV1Interface\n\tvar ssc ssclient.Interface\n\tvar ns string\n\tconst secretName = \"testsecret\"\n\tvar ss *ssv1alpha1.SealedSecret\n\tvar s *v1.Secret\n\tvar pubKey *rsa.PublicKey\n\tvar (\n\t\tctx       context.Context\n\t\tcancelLog context.CancelFunc\n\t)\n\n\tBeforeEach(func() {\n\t\tctx, cancelLog = context.WithCancel(context.Background())\n\n\t\tconf := clusterConfigOrDie()\n\t\tc = corev1.NewForConfigOrDie(conf)\n\t\tssc = ssclient.NewForConfigOrDie(conf)\n\t\tns = createNsOrDie(ctx, c, \"create\")\n\n\t\tgo streamLog(ctx, c, ns, \"sealed-secrets-controller\", \"sealed-secrets-controller\", GinkgoWriter, fmt.Sprintf(\"[%s] \", ns))\n\n\t\ts = &v1.Secret{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tNamespace: ns,\n\t\t\t\tName:      secretName,\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\"mylabel\": \"myvalue\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tData: map[string][]byte{\n\t\t\t\t\"foo\": []byte(\"bar\"),\n\t\t\t},\n\t\t}\n\n\t\t_, certs, err := fetchKeys(ctx, c)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tpubKey = certs[0].PublicKey.(*rsa.PublicKey)\n\n\t\tfmt.Fprintf(GinkgoWriter, \"Sealing Secret %#v\\n\", s)\n\t\tss, err = ssv1alpha1.NewSealedSecret(scheme.Codecs, pubKey, s)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t})\n\tAfterEach(func() {\n\t\tdeleteNsOrDie(ctx, c, ns)\n\t\tcancelLog()\n\t})\n\n\tJustBeforeEach(func() {\n\t\tvar err error\n\t\tfmt.Fprintf(GinkgoWriter, \"Creating SealedSecret: %#v\\n\", ss)\n\t\tss, err = ssc.BitnamiV1alpha1().SealedSecrets(ss.Namespace).Create(context.Background(), ss, metav1.CreateOptions{})\n\t\tExpect(err).NotTo(HaveOccurred())\n\t})\n\n\tDescribe(\"Simple change\", func() {\n\t\tContext(\"With no existing object (create)\", func() {\n\t\t\tIt(\"should produce expected Secret\", func() {\n\t\t\t\texpected := map[string][]byte{\n\t\t\t\t\t\"foo\": []byte(\"bar\"),\n\t\t\t\t}\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getData, Equal(expected)))\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(metav1.Object.GetLabels,\n\t\t\t\t\tHaveKeyWithValue(\"mylabel\", \"myvalue\")))\n\t\t\t\tEventually(func() (*ssv1alpha1.SealedSecret, error) {\n\t\t\t\t\treturn ssc.BitnamiV1alpha1().SealedSecrets(ns).Get(context.Background(), secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).ShouldNot(WithTransform(getStatus, BeNil()))\n\t\t\t\tEventually(func() (*ssv1alpha1.SealedSecret, error) {\n\t\t\t\t\treturn ssc.BitnamiV1alpha1().SealedSecrets(ns).Get(context.Background(), secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(compareLastTimes, Equal(true)))\n\t\t\t\tEventually(func() (*v1.EventList, error) {\n\t\t\t\t\treturn c.Events(ns).Search(scheme.Scheme, ss)\n\t\t\t\t}, Timeout, PollingInterval).Should(\n\t\t\t\t\tcontainEventWithReason(Equal(\"Unsealed\")))\n\t\t\t})\n\t\t})\n\n\t\tContext(\"With existing object (update)\", func() {\n\t\t\tJustBeforeEach(func() {\n\t\t\t\tvar err error\n\n\t\t\t\tEventually(func() (*ssv1alpha1.SealedSecret, error) {\n\t\t\t\t\treturn ssc.BitnamiV1alpha1().SealedSecrets(ss.Namespace).Get(context.Background(), secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).ShouldNot(WithTransform(getStatus, BeNil()))\n\n\t\t\t\tss, err = ssc.BitnamiV1alpha1().SealedSecrets(ss.Namespace).Get(context.Background(), secretName, metav1.GetOptions{})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tresVer := ss.ResourceVersion\n\n\t\t\t\t// update\n\t\t\t\ts.Data[\"foo\"] = []byte(\"baz\")\n\t\t\t\tss, err = ssv1alpha1.NewSealedSecret(scheme.Codecs, pubKey, s)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t\tss.ResourceVersion = resVer\n\n\t\t\t\ttime.Sleep(1 * time.Second)\n\t\t\t\tfmt.Fprintf(GinkgoWriter, \"Updating to SealedSecret: %#v\\n\", ss)\n\t\t\t\tss, err = ssc.BitnamiV1alpha1().SealedSecrets(ss.Namespace).Update(context.Background(), ss, metav1.UpdateOptions{})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t})\n\n\t\t\tIt(\"should produce updated Secret\", func() {\n\t\t\t\texpected := map[string][]byte{\n\t\t\t\t\t\"foo\": []byte(\"baz\"),\n\t\t\t\t}\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getData, Equal(expected)))\n\t\t\t\tEventually(func() (*ssv1alpha1.SealedSecret, error) {\n\t\t\t\t\treturn ssc.BitnamiV1alpha1().SealedSecrets(ns).Get(context.Background(), secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).ShouldNot(WithTransform(getStatus, BeNil()))\n\t\t\t\tEventually(func() (*ssv1alpha1.SealedSecret, error) {\n\t\t\t\t\treturn ssc.BitnamiV1alpha1().SealedSecrets(ns).Get(context.Background(), secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getObservedGeneration, Equal(int64(2))))\n\t\t\t\tEventually(func() (*ssv1alpha1.SealedSecret, error) {\n\t\t\t\t\treturn ssc.BitnamiV1alpha1().SealedSecrets(ns).Get(context.Background(), secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(compareLastTimes, Equal(false)))\n\t\t\t})\n\t\t})\n\n\t\tContext(\"With renamed encrypted keys\", func() {\n\t\t\tBeforeEach(func() {\n\t\t\t\tss.Spec.EncryptedData = map[string]string{\n\t\t\t\t\t\"xyzzy\": ss.Spec.EncryptedData[\"foo\"],\n\t\t\t\t}\n\t\t\t})\n\t\t\tIt(\"should produce expected Secret\", func() {\n\t\t\t\texpected := map[string][]byte{\n\t\t\t\t\t// renamed key\n\t\t\t\t\t\"xyzzy\": []byte(\"bar\"),\n\t\t\t\t}\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getData, Equal(expected)))\n\t\t\t\tEventually(func() (*ssv1alpha1.SealedSecret, error) {\n\t\t\t\t\treturn ssc.BitnamiV1alpha1().SealedSecrets(ns).Get(context.Background(), secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).ShouldNot(WithTransform(getStatus, BeNil()))\n\t\t\t})\n\t\t})\n\n\t\tContext(\"With appended encrypted keys\", func() {\n\t\t\tBeforeEach(func() {\n\t\t\t\tlabel := fmt.Sprintf(\"%s/%s\", s.Namespace, s.Name)\n\t\t\t\tciphertext, err := crypto.HybridEncrypt(rand.Reader, pubKey, []byte(\"new!\"), []byte(label))\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\t\tss.Spec.EncryptedData[\"foo2\"] = base64.StdEncoding.EncodeToString(ciphertext)\n\t\t\t})\n\t\t\tIt(\"should produce expected Secret\", func() {\n\t\t\t\texpected := map[string][]byte{\n\t\t\t\t\t\"foo\":  []byte(\"bar\"),\n\t\t\t\t\t\"foo2\": []byte(\"new!\"),\n\t\t\t\t}\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getData, Equal(expected)))\n\t\t\t\tEventually(func() (*ssv1alpha1.SealedSecret, error) {\n\t\t\t\t\treturn ssc.BitnamiV1alpha1().SealedSecrets(ns).Get(context.Background(), secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).ShouldNot(WithTransform(getStatus, BeNil()))\n\t\t\t})\n\t\t})\n\t})\n\n\tDescribe(\"Secret already exists\", func() {\n\t\tContext(\"With managed annotation\", func() {\n\t\t\tBeforeEach(func() {\n\t\t\t\ts.Data = map[string][]byte{\n\t\t\t\t\t\"foo\":  []byte(\"bar1\"),\n\t\t\t\t\t\"foo2\": []byte(\"bar2\"),\n\t\t\t\t}\n\t\t\t\ts.Annotations = map[string]string{\n\t\t\t\t\tssv1alpha1.SealedSecretManagedAnnotation: \"true\",\n\t\t\t\t}\n\t\t\t\ts.Labels[\"anotherlabel\"] = \"anothervalue\"\n\t\t\t\tc.Secrets(ns).Create(ctx, s, metav1.CreateOptions{})\n\t\t\t})\n\t\t\tIt(\"should take ownership of the existing Secret overwriting the whole Secret\", func() {\n\t\t\t\texpectedData := map[string][]byte{\n\t\t\t\t\t\"foo\": []byte(\"bar\"),\n\t\t\t\t}\n\t\t\t\tvar expectedAnnotations map[string]string\n\t\t\t\texpectedLabels := map[string]string{\n\t\t\t\t\t\"mylabel\": \"myvalue\",\n\t\t\t\t}\n\t\t\t\tEventually(func() (*v1.EventList, error) {\n\t\t\t\t\treturn c.Events(ns).Search(scheme.Scheme, ss)\n\t\t\t\t}, Timeout, PollingInterval).Should(\n\t\t\t\t\tcontainEventWithReason(Equal(\"Unsealed\")),\n\t\t\t\t)\n\t\t\t\tEventually(func() (*ssv1alpha1.SealedSecret, error) {\n\t\t\t\t\treturn ssc.BitnamiV1alpha1().SealedSecrets(ns).Get(context.Background(), secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).ShouldNot(WithTransform(getStatus, BeNil()))\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getFirstOwnerName, Equal(ss.GetName())))\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getData, Equal(expectedData)))\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getAnnotations, Equal(expectedAnnotations)))\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getLabels, Equal(expectedLabels)))\n\t\t\t})\n\t\t})\n\n\t\tContext(\"With managed and patch annotation\", func() {\n\t\t\tBeforeEach(func() {\n\t\t\t\ts.Data = map[string][]byte{\n\t\t\t\t\t\"foo\":  []byte(\"bar1\"),\n\t\t\t\t\t\"foo2\": []byte(\"bar2\"),\n\t\t\t\t}\n\t\t\t\ts.Annotations = map[string]string{\n\t\t\t\t\tssv1alpha1.SealedSecretManagedAnnotation: \"true\",\n\t\t\t\t\tssv1alpha1.SealedSecretPatchAnnotation:   \"true\",\n\t\t\t\t}\n\t\t\t\ts.Labels[\"anotherlabel\"] = \"anothervalue\"\n\t\t\t\tc.Secrets(ns).Create(ctx, s, metav1.CreateOptions{})\n\t\t\t})\n\n\t\t\tIt(\"should take ownership of the existing Secret patching instead of overwriting the whole Secret\", func() {\n\t\t\t\texpectedData := map[string][]byte{\n\t\t\t\t\t\"foo\":  []byte(\"bar\"),\n\t\t\t\t\t\"foo2\": []byte(\"bar2\"),\n\t\t\t\t}\n\t\t\t\texpectedAnnotations := map[string]string{\n\t\t\t\t\tssv1alpha1.SealedSecretManagedAnnotation: \"true\",\n\t\t\t\t\tssv1alpha1.SealedSecretPatchAnnotation:   \"true\",\n\t\t\t\t}\n\t\t\t\texpectedLabels := map[string]string{\n\t\t\t\t\t\"mylabel\":      \"myvalue\",\n\t\t\t\t\t\"anotherlabel\": \"anothervalue\",\n\t\t\t\t}\n\t\t\t\tEventually(func() (*v1.EventList, error) {\n\t\t\t\t\treturn c.Events(ns).Search(scheme.Scheme, ss)\n\t\t\t\t}, Timeout, PollingInterval).Should(\n\t\t\t\t\tcontainEventWithReason(Equal(\"Unsealed\")),\n\t\t\t\t)\n\t\t\t\tEventually(func() (*ssv1alpha1.SealedSecret, error) {\n\t\t\t\t\treturn ssc.BitnamiV1alpha1().SealedSecrets(ns).Get(context.Background(), secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).ShouldNot(WithTransform(getStatus, BeNil()))\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getFirstOwnerName, Equal(ss.GetName())))\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getData, Equal(expectedData)))\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getAnnotations, Equal(expectedAnnotations)))\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getLabels, Equal(expectedLabels)))\n\t\t\t})\n\t\t})\n\n\t\tContext(\"With patch annotation\", func() {\n\t\t\tBeforeEach(func() {\n\t\t\t\ts.Data = map[string][]byte{\n\t\t\t\t\t\"foo\":  []byte(\"bar1\"),\n\t\t\t\t\t\"foo2\": []byte(\"bar2\"),\n\t\t\t\t}\n\t\t\t\ts.Annotations = map[string]string{\n\t\t\t\t\tssv1alpha1.SealedSecretPatchAnnotation: \"true\",\n\t\t\t\t}\n\t\t\t\ts.Labels[\"anotherlabel\"] = \"anothervalue\"\n\t\t\t\tc.Secrets(ns).Create(ctx, s, metav1.CreateOptions{})\n\t\t\t})\n\n\t\t\tIt(\"should not take ownership of existing Secret while patching the Secret\", func() {\n\t\t\t\texpectedData := map[string][]byte{\n\t\t\t\t\t\"foo\":  []byte(\"bar\"),\n\t\t\t\t\t\"foo2\": []byte(\"bar2\"),\n\t\t\t\t}\n\t\t\t\texpectedAnnotations := map[string]string{\n\t\t\t\t\tssv1alpha1.SealedSecretPatchAnnotation: \"true\",\n\t\t\t\t}\n\t\t\t\texpectedLabels := map[string]string{\n\t\t\t\t\t\"mylabel\":      \"myvalue\",\n\t\t\t\t\t\"anotherlabel\": \"anothervalue\",\n\t\t\t\t}\n\t\t\t\tEventually(func() (*v1.EventList, error) {\n\t\t\t\t\treturn c.Events(ns).Search(scheme.Scheme, ss)\n\t\t\t\t}, Timeout, PollingInterval).Should(\n\t\t\t\t\tcontainEventWithReason(Equal(\"Unsealed\")),\n\t\t\t\t)\n\t\t\t\tEventually(func() (*ssv1alpha1.SealedSecret, error) {\n\t\t\t\t\treturn ssc.BitnamiV1alpha1().SealedSecrets(ns).Get(context.Background(), secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).ShouldNot(WithTransform(getStatus, BeNil()))\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getNumberOfOwners, Equal(0)))\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getData, Equal(expectedData)))\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getAnnotations, Equal(expectedAnnotations)))\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getLabels, Equal(expectedLabels)))\n\t\t\t})\n\t\t})\n\n\t\tContext(\"With patch annotation and empty secret\", func() {\n\t\t\tBeforeEach(func() {\n\t\t\t\t// Empty secret has no data nor labels field\n\t\t\t\ts.Data = nil\n\t\t\t\ts.Labels = nil\n\t\t\t\ts.Annotations = map[string]string{\n\t\t\t\t\tssv1alpha1.SealedSecretPatchAnnotation: \"true\",\n\t\t\t\t}\n\t\t\t\tc.Secrets(ns).Create(ctx, s, metav1.CreateOptions{})\n\t\t\t})\n\n\t\t\tIt(\"should not take ownership of existing Secret while patching the Secret\", func() {\n\t\t\t\texpectedData := map[string][]byte{\n\t\t\t\t\t\"foo\": []byte(\"bar\"),\n\t\t\t\t}\n\t\t\t\texpectedAnnotations := map[string]string{\n\t\t\t\t\tssv1alpha1.SealedSecretPatchAnnotation: \"true\",\n\t\t\t\t}\n\t\t\t\texpectedLabels := map[string]string{\n\t\t\t\t\t\"mylabel\": \"myvalue\",\n\t\t\t\t}\n\t\t\t\tEventually(func() (*v1.EventList, error) {\n\t\t\t\t\treturn c.Events(ns).Search(scheme.Scheme, ss)\n\t\t\t\t}, Timeout, PollingInterval).Should(\n\t\t\t\t\tcontainEventWithReason(Equal(\"Unsealed\")),\n\t\t\t\t)\n\t\t\t\tEventually(func() (*ssv1alpha1.SealedSecret, error) {\n\t\t\t\t\treturn ssc.BitnamiV1alpha1().SealedSecrets(ns).Get(context.Background(), secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).ShouldNot(WithTransform(getStatus, BeNil()))\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getNumberOfOwners, Equal(0)))\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getData, Equal(expectedData)))\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getAnnotations, Equal(expectedAnnotations)))\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getLabels, Equal(expectedLabels)))\n\t\t\t})\n\t\t})\n\t})\n\n\tDescribe(\"Secret Recreation\", func() {\n\t\tContext(\"With owned secret\", func() {\n\t\t\tJustBeforeEach(func() {\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getFirstOwnerName, Equal(ss.GetName())))\n\t\t\t\terr := c.Secrets(ns).Delete(ctx, secretName, metav1.DeleteOptions{})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t})\n\t\t\tIt(\"should recreate the secret\", func() {\n\t\t\t\texpected := map[string][]byte{\n\t\t\t\t\t\"foo\": []byte(\"bar\"),\n\t\t\t\t}\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getData, Equal(expected)))\n\t\t\t\tEventually(func() (*v1.EventList, error) {\n\t\t\t\t\treturn c.Events(ns).Search(scheme.Scheme, ss)\n\t\t\t\t}, Timeout, PollingInterval).Should(\n\t\t\t\t\tcontainEventWithReason(Equal(\"Unsealed\")),\n\t\t\t\t)\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getFirstOwnerName, Equal(ss.GetName())))\n\t\t\t})\n\t\t})\n\n\t\tContext(\"With unowned secret with managed annotation\", func() {\n\t\t\tBeforeEach(func() {\n\t\t\t\ts.Data[\"foo2\"] = []byte(\"bar2\")\n\t\t\t\ts.Annotations = map[string]string{\n\t\t\t\t\tssv1alpha1.SealedSecretManagedAnnotation: \"true\",\n\t\t\t\t}\n\t\t\t\tc.Secrets(ns).Create(ctx, s, metav1.CreateOptions{})\n\t\t\t})\n\t\t\tJustBeforeEach(func() {\n\t\t\t\terr := c.Secrets(ns).Delete(ctx, secretName, metav1.DeleteOptions{})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t})\n\t\t\tIt(\"should recreate the secret\", func() {\n\t\t\t\texpected := map[string][]byte{\n\t\t\t\t\t\"foo\": []byte(\"bar\"),\n\t\t\t\t}\n\t\t\t\tEventually(func() (*v1.EventList, error) {\n\t\t\t\t\treturn c.Events(ns).Search(scheme.Scheme, ss)\n\t\t\t\t}, Timeout, PollingInterval).Should(\n\t\t\t\t\tcontainEventWithReason(Equal(\"Unsealed\")),\n\t\t\t\t)\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getFirstOwnerName, Equal(ss.GetName())))\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getData, Equal(expected)))\n\t\t\t})\n\t\t})\n\n\t\tContext(\"With unowned secret without managed annotation\", func() {\n\t\t\tBeforeEach(func() {\n\t\t\t\ts.Annotations = map[string]string{}\n\t\t\t\tc.Secrets(ns).Create(ctx, s, metav1.CreateOptions{})\n\t\t\t})\n\t\t\tJustBeforeEach(func() {\n\t\t\t\terr := c.Secrets(ns).Delete(ctx, secretName, metav1.DeleteOptions{})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t})\n\t\t\tIt(\"should not recreate the secret\", func() {\n\t\t\t\tConsistently(func() error {\n\t\t\t\t\t_, err := c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t\treturn err\n\t\t\t\t}).Should(WithTransform(errors.IsNotFound, Equal(true)))\n\t\t\t})\n\t\t})\n\n\t\tContext(\"With unowned secret with patch annotation\", func() {\n\t\t\tBeforeEach(func() {\n\t\t\t\ts.Data = map[string][]byte{\n\t\t\t\t\t\"foo\":  []byte(\"bar1\"),\n\t\t\t\t\t\"foo2\": []byte(\"bar2\"),\n\t\t\t\t}\n\t\t\t\ts.Annotations = map[string]string{\n\t\t\t\t\tssv1alpha1.SealedSecretPatchAnnotation: \"true\",\n\t\t\t\t}\n\t\t\t\ts.Labels[\"anotherlabel\"] = \"anothervalue\"\n\t\t\t\tc.Secrets(ns).Create(ctx, s, metav1.CreateOptions{})\n\t\t\t})\n\t\t\tJustBeforeEach(func() {\n\t\t\t\terr := c.Secrets(ns).Delete(ctx, secretName, metav1.DeleteOptions{})\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t})\n\n\t\t\tIt(\"should not recreate the secret\", func() {\n\t\t\t\tConsistently(func() error {\n\t\t\t\t\t_, err := c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t\treturn err\n\t\t\t\t}).Should(WithTransform(errors.IsNotFound, Equal(true)))\n\t\t\t})\n\t\t})\n\t})\n\n\tDescribe(\"Same name, wrong key\", func() {\n\t\tBeforeEach(func() {\n\t\t\t// NB: weak key-size - this is just a test case\n\t\t\twrongKey, err := rsa.GenerateKey(rand.Reader, 1024)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tfmt.Fprintf(GinkgoWriter, \"Resealing with wrong key\\n\")\n\t\t\tss, err = ssv1alpha1.NewSealedSecret(scheme.Codecs, &wrongKey.PublicKey, s)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should *not* produce a Secret\", func() {\n\t\t\tConsistently(func() error {\n\t\t\t\t_, err := c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\treturn err\n\t\t\t}).Should(WithTransform(errors.IsNotFound, Equal(true)))\n\t\t})\n\n\t\tIt(\"should produce an error Event\", func() {\n\t\t\t// Check for a suitable error event on the\n\t\t\t// SealedSecret\n\t\t\tEventually(func() (*v1.EventList, error) {\n\t\t\t\treturn c.Events(ns).Search(scheme.Scheme, ss)\n\t\t\t}, Timeout, PollingInterval).Should(\n\t\t\t\tcontainEventWithReason(Equal(\"ErrUnsealFailed\")),\n\t\t\t)\n\t\t})\n\t})\n\n\tDescribe(\"Custom Secret Type\", func() {\n\t\tBeforeEach(func() {\n\t\t\tlabel := fmt.Sprintf(\"%s/%s\", s.Namespace, s.Name)\n\t\t\tciphertext, err := crypto.HybridEncrypt(rand.Reader, pubKey, []byte(\"{\\\"auths\\\": {\\\"https://index.docker.io/v1/\\\": {\\\"auth\\\": \\\"c3R...zE2\\\"}}}\"), []byte(label))\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tss.Spec.EncryptedData[\".dockerconfigjson\"] = base64.StdEncoding.EncodeToString(ciphertext)\n\t\t\tdelete(ss.Spec.EncryptedData, \"foo\")\n\t\t\tss.Spec.Template.Type = \"kubernetes.io/dockerconfigjson\"\n\t\t})\n\n\t\tIt(\"should produce expected Secret\", func() {\n\t\t\texpected := map[string][]byte{\n\t\t\t\t\".dockerconfigjson\": []byte(\"{\\\"auths\\\": {\\\"https://index.docker.io/v1/\\\": {\\\"auth\\\": \\\"c3R...zE2\\\"}}}\"),\n\t\t\t}\n\t\t\tvar expectedType v1.SecretType = \"kubernetes.io/dockerconfigjson\"\n\n\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getData, Equal(expected)))\n\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getSecretType, Equal(expectedType)))\n\t\t\tEventually(func() (*ssv1alpha1.SealedSecret, error) {\n\t\t\t\treturn ssc.BitnamiV1alpha1().SealedSecrets(ns).Get(context.Background(), secretName, metav1.GetOptions{})\n\t\t\t}, Timeout, PollingInterval).ShouldNot(WithTransform(getStatus, BeNil()))\n\t\t\tEventually(func() (*v1.EventList, error) {\n\t\t\t\treturn c.Events(ns).Search(scheme.Scheme, ss)\n\t\t\t}, Timeout, PollingInterval).Should(\n\t\t\t\tcontainEventWithReason(Equal(\"Unsealed\")),\n\t\t\t)\n\t\t})\n\t})\n\n\tDescribe(\"Immutable Secret\", func() {\n\t\tBeforeEach(func() {\n\t\t\tss.Spec.Template.Immutable = new(bool)\n\t\t\t*ss.Spec.Template.Immutable = true\n\t\t})\n\n\t\tIt(\"should produce expected Secret\", func() {\n\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getSecretImmutable, Equal(true)))\n\t\t\tEventually(func() (*ssv1alpha1.SealedSecret, error) {\n\t\t\t\treturn ssc.BitnamiV1alpha1().SealedSecrets(ns).Get(context.Background(), secretName, metav1.GetOptions{})\n\t\t\t}, Timeout, PollingInterval).ShouldNot(WithTransform(getStatus, BeNil()))\n\t\t\tEventually(func() (*v1.EventList, error) {\n\t\t\t\treturn c.Events(ns).Search(scheme.Scheme, ss)\n\t\t\t}, Timeout, PollingInterval).Should(\n\t\t\t\tcontainEventWithReason(Equal(\"Unsealed\")),\n\t\t\t)\n\t\t})\n\t})\n\n\tDescribe(\"Immutable Secret Error\", func() {\n\t\tBeforeEach(func() {\n\t\t\tss.Spec.Template.Immutable = new(bool)\n\t\t\t*ss.Spec.Template.Immutable = true\n\t\t})\n\n\t\tJustBeforeEach(func() {\n\t\t\tvar err error\n\n\t\t\tEventually(func() (*ssv1alpha1.SealedSecret, error) {\n\t\t\t\treturn ssc.BitnamiV1alpha1().SealedSecrets(ss.Namespace).Get(context.Background(), secretName, metav1.GetOptions{})\n\t\t\t}, Timeout, PollingInterval).ShouldNot(WithTransform(getStatus, BeNil()))\n\n\t\t\tss, err = ssc.BitnamiV1alpha1().SealedSecrets(ss.Namespace).Get(context.Background(), secretName, metav1.GetOptions{})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tresVer := ss.ResourceVersion\n\n\t\t\t// update\n\t\t\ts.Data[\"foo\"] = []byte(\"baz\")\n\t\t\tss, err = ssv1alpha1.NewSealedSecret(scheme.Codecs, pubKey, s)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tss.ResourceVersion = resVer\n\n\t\t\tfmt.Fprintf(GinkgoWriter, \"Updating to SealedSecret: %#v\\n\", ss)\n\t\t\tss, err = ssc.BitnamiV1alpha1().SealedSecrets(ss.Namespace).Update(context.Background(), ss, metav1.UpdateOptions{})\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\n\t\tIt(\"should record update failure as an event\", func() {\n\t\t\tEventually(func() (*ssv1alpha1.SealedSecret, error) {\n\t\t\t\treturn ssc.BitnamiV1alpha1().SealedSecrets(ns).Get(context.Background(), secretName, metav1.GetOptions{})\n\t\t\t}, Timeout, PollingInterval).ShouldNot(WithTransform(getStatus, BeNil()))\n\t\t\tEventually(func() (*ssv1alpha1.SealedSecret, error) {\n\t\t\t\treturn ssc.BitnamiV1alpha1().SealedSecrets(ns).Get(context.Background(), secretName, metav1.GetOptions{})\n\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getObservedGeneration, Equal(int64(2))))\n\t\t\tEventually(func() (*v1.EventList, error) {\n\t\t\t\treturn c.Events(ns).Search(scheme.Scheme, ss)\n\t\t\t}, Timeout, PollingInterval).Should(\n\t\t\t\tcontainEventWithReason(Equal(\"ErrUpdateFailed\")),\n\t\t\t)\n\t\t\tEventually(func() (*v1.EventList, error) {\n\t\t\t\treturn c.Events(ns).Search(scheme.Scheme, ss)\n\t\t\t}, Timeout, PollingInterval).Should(\n\t\t\t\tcontainEventWithMessage(ContainSubstring(\"the target Secret is immutable\")),\n\t\t\t)\n\t\t})\n\t})\n\n\tDescribe(\"Different name/namespace\", func() {\n\t\tContext(\"With wrong name\", func() {\n\t\t\tconst secretName2 = \"not-testsecret\"\n\t\t\tBeforeEach(func() {\n\t\t\t\tss.Name = secretName2\n\t\t\t})\n\t\t\tIt(\"should *not* produce a Secret\", func() {\n\t\t\t\tConsistently(func() error {\n\t\t\t\t\t_, err := c.Secrets(ns).Get(ctx, secretName2, metav1.GetOptions{})\n\t\t\t\t\treturn err\n\t\t\t\t}).Should(WithTransform(errors.IsNotFound, Equal(true)))\n\t\t\t})\n\n\t\t\tIt(\"should produce an error Event\", func() {\n\t\t\t\t// Check for a suitable error event on the\n\t\t\t\t// SealedSecret\n\t\t\t\tEventually(func() (*v1.EventList, error) {\n\t\t\t\t\treturn c.Events(ns).Search(scheme.Scheme, ss)\n\t\t\t\t}, Timeout, PollingInterval).Should(\n\t\t\t\t\tcontainEventWithReason(Equal(\"ErrUnsealFailed\")),\n\t\t\t\t)\n\t\t\t})\n\t\t})\n\n\t\tContext(\"With wrong namespace\", func() {\n\t\t\tvar ns2 string\n\t\t\tBeforeEach(func() {\n\t\t\t\tns2 = createNsOrDie(ctx, c, \"create\")\n\t\t\t\tss.Namespace = ns2\n\t\t\t})\n\t\t\tAfterEach(func() {\n\t\t\t\tdeleteNsOrDie(ctx, c, ns2)\n\t\t\t})\n\n\t\t\tIt(\"should *not* produce a Secret\", func() {\n\t\t\t\tConsistently(func() error {\n\t\t\t\t\t_, err := c.Secrets(ns2).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t\treturn err\n\t\t\t\t}).Should(WithTransform(errors.IsNotFound, Equal(true)))\n\t\t\t})\n\n\t\t\tIt(\"should produce an error Event\", func() {\n\t\t\t\t// Check for a suitable error event on the\n\t\t\t\t// SealedSecret\n\t\t\t\tEventually(func() (*v1.EventList, error) {\n\t\t\t\t\treturn c.Events(ns2).Search(scheme.Scheme, ss)\n\t\t\t\t}, Timeout, PollingInterval).Should(\n\t\t\t\t\tcontainEventWithReason(Equal(\"ErrUnsealFailed\")),\n\t\t\t\t)\n\t\t\t})\n\t\t})\n\n\t\tContext(\"With wrong name and cluster-wide annotation\", func() {\n\t\t\tconst secretName2 = \"not-testsecret\"\n\t\t\tBeforeEach(func() {\n\t\t\t\tvar err error\n\n\t\t\t\ts.Annotations = map[string]string{\n\t\t\t\t\tssv1alpha1.SealedSecretClusterWideAnnotation: \"true\",\n\t\t\t\t}\n\n\t\t\t\tfmt.Fprintf(GinkgoWriter, \"Re-sealing secret %#v\\n\", s)\n\t\t\t\tss, err = ssv1alpha1.NewSealedSecret(scheme.Codecs, pubKey, s)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t})\n\t\t\tBeforeEach(func() {\n\t\t\t\tss.Name = secretName2\n\t\t\t})\n\t\t\tIt(\"should produce expected Secret\", func() {\n\t\t\t\texpected := map[string][]byte{\n\t\t\t\t\t\"foo\": []byte(\"bar\"),\n\t\t\t\t}\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName2, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getData, Equal(expected)))\n\t\t\t\tEventually(func() (*ssv1alpha1.SealedSecret, error) {\n\t\t\t\t\treturn ssc.BitnamiV1alpha1().SealedSecrets(ns).Get(context.Background(), secretName2, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).ShouldNot(WithTransform(getStatus, BeNil()))\n\t\t\t})\n\t\t})\n\n\t\tContext(\"With wrong namespace and cluster-wide annotation\", func() {\n\t\t\tvar ns2 string\n\t\t\tBeforeEach(func() {\n\t\t\t\tns2 = createNsOrDie(ctx, c, \"create\")\n\t\t\t})\n\t\t\tBeforeEach(func() {\n\t\t\t\tvar err error\n\n\t\t\t\ts.Annotations = map[string]string{\n\t\t\t\t\tssv1alpha1.SealedSecretClusterWideAnnotation: \"true\",\n\t\t\t\t}\n\n\t\t\t\tfmt.Fprintf(GinkgoWriter, \"Re-sealing secret %#v\\n\", s)\n\t\t\t\tss, err = ssv1alpha1.NewSealedSecret(scheme.Codecs, pubKey, s)\n\t\t\t\tss.Namespace = ns2\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t})\n\t\t\tAfterEach(func() {\n\t\t\t\tdeleteNsOrDie(ctx, c, ns2)\n\t\t\t})\n\t\t\tIt(\"should produce expected Secret\", func() {\n\t\t\t\texpected := map[string][]byte{\n\t\t\t\t\t\"foo\": []byte(\"bar\"),\n\t\t\t\t}\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns2).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getData, Equal(expected)))\n\t\t\t\tEventually(func() (*ssv1alpha1.SealedSecret, error) {\n\t\t\t\t\treturn ssc.BitnamiV1alpha1().SealedSecrets(ns2).Get(context.Background(), secretName, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).ShouldNot(WithTransform(getStatus, BeNil()))\n\t\t\t})\n\t\t})\n\n\t\tContext(\"With wrong name and namespace-wide annotation\", func() {\n\t\t\tconst secretName2 = \"not-testsecret\"\n\t\t\tBeforeEach(func() {\n\t\t\t\tvar err error\n\n\t\t\t\ts.Annotations = map[string]string{\n\t\t\t\t\tssv1alpha1.SealedSecretNamespaceWideAnnotation: \"true\",\n\t\t\t\t}\n\n\t\t\t\tfmt.Fprintf(GinkgoWriter, \"Re-sealing secret %#v\\n\", s)\n\t\t\t\tss, err = ssv1alpha1.NewSealedSecret(scheme.Codecs, pubKey, s)\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t})\n\t\t\tBeforeEach(func() {\n\t\t\t\tss.Name = secretName2\n\t\t\t})\n\t\t\tIt(\"should produce expected Secret\", func() {\n\t\t\t\texpected := map[string][]byte{\n\t\t\t\t\t\"foo\": []byte(\"bar\"),\n\t\t\t\t}\n\t\t\t\tEventually(func() (*v1.Secret, error) {\n\t\t\t\t\treturn c.Secrets(ns).Get(ctx, secretName2, metav1.GetOptions{})\n\t\t\t\t}, Timeout, PollingInterval).Should(WithTransform(getData, Equal(expected)))\n\t\t\t})\n\t\t})\n\n\t\tContext(\"With wrong namespace and namespace-wide annotation\", func() {\n\t\t\tvar ns2 string\n\t\t\tBeforeEach(func() {\n\t\t\t\tns2 = createNsOrDie(ctx, c, \"create\")\n\t\t\t})\n\t\t\tBeforeEach(func() {\n\t\t\t\tvar err error\n\n\t\t\t\ts.Annotations = map[string]string{\n\t\t\t\t\tssv1alpha1.SealedSecretNamespaceWideAnnotation: \"true\",\n\t\t\t\t}\n\n\t\t\t\tfmt.Fprintf(GinkgoWriter, \"Re-sealing secret %#v\\n\", s)\n\t\t\t\tss, err = ssv1alpha1.NewSealedSecret(scheme.Codecs, pubKey, s)\n\t\t\t\tss.Namespace = ns2\n\t\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\t})\n\t\t\tAfterEach(func() {\n\t\t\t\tdeleteNsOrDie(ctx, c, ns2)\n\t\t\t})\n\n\t\t\tIt(\"should *not* produce a Secret\", func() {\n\t\t\t\tConsistently(func() error {\n\t\t\t\t\t_, err := c.Secrets(ns2).Get(ctx, secretName, metav1.GetOptions{})\n\t\t\t\t\treturn err\n\t\t\t\t}).Should(WithTransform(errors.IsNotFound, Equal(true)))\n\t\t\t})\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"controller --version\", func() {\n\tvar input io.Reader\n\tvar output *bytes.Buffer\n\tvar args []string\n\n\tBeforeEach(func() {\n\t\targs = []string{\"--version\"}\n\t\toutput = &bytes.Buffer{}\n\t})\n\n\tJustBeforeEach(func() {\n\t\terr := runController(args, input, output)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t})\n\n\tIt(\"should produce the version\", func() {\n\t\tExpect(output.String()).Should(MatchRegexp(\"^controller version: (v[0-9]+\\\\.[0-9]+\\\\.[0-9]+|[0-9a-f]{40})(\\\\+dirty)?\"))\n\t})\n})\n"
  },
  {
    "path": "integration/integration_suite_test.go",
    "content": "//go:build integration\n// +build integration\n\npackage integration\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"os/exec\"\n\t\"testing\"\n\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\tcorev1 \"k8s.io/client-go/kubernetes/typed/core/v1\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\n\tssv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/apis/sealedsecrets/v1alpha1\"\n\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\n\t// For client auth plugins\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth\"\n)\n\nvar kubeconfig = flag.String(\"kubeconfig\", \"\", \"absolute path to the kubeconfig file\")\nvar controllerNs = flag.String(\"namespace\", \"kube-system\", \"namespace where the controller is installed\")\nvar kubesealBin = flag.String(\"kubeseal-bin\", \"kubeseal\", \"path to kubeseal executable under test\")\nvar controllerBin = flag.String(\"controller-bin\", \"controller\", \"path to controller executable under test\")\n\nfunc clusterConfigOrDie() *rest.Config {\n\tvar config *rest.Config\n\tvar err error\n\n\tif *kubeconfig != \"\" {\n\t\tconfig, err = clientcmd.BuildConfigFromFlags(\"\", *kubeconfig)\n\t} else {\n\t\tconfig, err = rest.InClusterConfig()\n\t}\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\n\treturn config\n}\n\nfunc createNsOrDie(ctx context.Context, c corev1.NamespacesGetter, ns string) string {\n\tresult, err := c.Namespaces().Create(\n\t\tctx,\n\t\t&v1.Namespace{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tGenerateName: ns,\n\t\t\t},\n\t\t}, metav1.CreateOptions{})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tname := result.GetName()\n\tfmt.Fprintf(GinkgoWriter, \"Created namespace %s\\n\", name)\n\treturn name\n}\n\nfunc deleteNsOrDie(ctx context.Context, c corev1.NamespacesGetter, ns string) {\n\terr := c.Namespaces().Delete(ctx, ns, metav1.DeleteOptions{})\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n}\n\nfunc containsString(haystack []string, needle string) bool {\n\tfor _, s := range haystack {\n\t\tif s == needle {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc runKubeseal(flags []string, input io.Reader, output io.Writer, opts ...runAppOpt) error {\n\targs := []string{}\n\tif *kubeconfig != \"\" && !containsString(flags, \"--kubeconfig\") {\n\t\targs = append(args, \"--kubeconfig\", *kubeconfig)\n\t}\n\targs = append(args, flags...)\n\n\treturn runApp(*kubesealBin, args, input, output, opts...)\n}\n\ntype interruptableReader struct {\n\tctx context.Context\n\tr   io.Reader\n}\n\nfunc (r interruptableReader) Read(p []byte) (int, error) {\n\tif err := r.ctx.Err(); err != nil {\n\t\treturn 0, err\n\t}\n\tn, err := r.r.Read(p)\n\tif err != nil {\n\t\treturn n, err\n\t}\n\treturn n, r.ctx.Err()\n}\n\nfunc streamLog(ctx context.Context, c corev1.PodsGetter, namespace, name, container string, output io.Writer, prefix string) error {\n\tzero := int64(0)\n\treadCloser, err := c.Pods(namespace).GetLogs(name, &v1.PodLogOptions{\n\t\tContainer:    container,\n\t\tFollow:       true,\n\t\tSinceSeconds: &zero,\n\t}).Stream(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer readCloser.Close()\n\n\tscanner := bufio.NewScanner(interruptableReader{ctx, readCloser})\n\tfor scanner.Scan() {\n\t\tfmt.Fprintf(output, \"%s%s\\n\", prefix, scanner.Text())\n\t}\n\treturn scanner.Err()\n}\n\nfunc runController(flags []string, input io.Reader, output io.Writer) error {\n\treturn runApp(*controllerBin, flags, input, output)\n}\n\ntype runAppOpt func(*runAppOpts)\n\ntype runAppOpts struct {\n\tstderr io.Writer\n}\n\nfunc runAppWithStderr(w io.Writer) runAppOpt {\n\treturn func(o *runAppOpts) { o.stderr = w }\n}\n\nfunc runApp(app string, flags []string, input io.Reader, output io.Writer, opts ...runAppOpt) error {\n\toptions := runAppOpts{\n\t\tstderr: GinkgoWriter,\n\t}\n\tfor _, o := range opts {\n\t\to(&options)\n\t}\n\n\tfmt.Fprintf(GinkgoWriter, \"Running %q %q\\n\", app, flags)\n\tcmd := exec.Command(app, flags...)\n\tcmd.Stdin = input\n\tcmd.Stdout = output\n\tcmd.Stderr = options.stderr\n\n\treturn cmd.Run()\n}\n\nfunc runKubesealWith(flags []string, input runtime.Object, opts ...runAppOpt) (runtime.Object, error) {\n\tenc := scheme.Codecs.LegacyCodec(v1.SchemeGroupVersion)\n\tindata, err := runtime.Encode(enc, input)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfmt.Fprintf(GinkgoWriter, \"kubeseal input:\\n%s\\n\", indata)\n\n\toutbuf := bytes.Buffer{}\n\n\tif err := runKubeseal(flags, bytes.NewReader(indata), &outbuf, opts...); err != nil {\n\t\treturn nil, err\n\t}\n\n\tfmt.Fprintf(GinkgoWriter, \"kubeseal output:\\n%s\\n\", outbuf.Bytes())\n\n\toutputObj, err := runtime.Decode(scheme.Codecs.UniversalDecoder(ssv1alpha1.SchemeGroupVersion), outbuf.Bytes())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn outputObj, nil\n}\n\nfunc TestE2e(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\tRunSpecs(t, \"sealed-secrets integration tests\")\n}\n"
  },
  {
    "path": "integration/kubeseal_test.go",
    "content": "//go:build integration\n// +build integration\n\npackage integration\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"encoding/json\"\n\t\"encoding/pem\"\n\t\"io\"\n\t\"os\"\n\n\tssv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/apis/sealedsecrets/v1alpha1\"\n\t. \"github.com/onsi/ginkgo/v2\"\n\t. \"github.com/onsi/gomega\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\tcorev1 \"k8s.io/client-go/kubernetes/typed/core/v1\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\tclientcmdapi \"k8s.io/client-go/tools/clientcmd/api\"\n\tclientcmdlatest \"k8s.io/client-go/tools/clientcmd/api/latest\"\n\tcertUtil \"k8s.io/client-go/util/cert\"\n)\n\nvar _ = Describe(\"kubeseal\", func() {\n\tvar c corev1.CoreV1Interface\n\tconst secretName = \"testSecret\"\n\tvar ns string\n\tvar input *v1.Secret\n\tvar ss *ssv1alpha1.SealedSecret\n\tvar args []string\n\tvar privKeys map[string]*rsa.PrivateKey\n\tvar certs []*x509.Certificate\n\tvar config *clientcmdapi.Config\n\tvar kubeconfigFile string\n\tvar (\n\t\tctx    context.Context\n\t\tcancel context.CancelFunc\n\t)\n\n\tBeforeEach(func() {\n\t\tctx, cancel = context.WithCancel(context.Background())\n\t\tclientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(\n\t\t\t&clientcmd.ClientConfigLoadingRules{ExplicitPath: *kubeconfig},\n\t\t\t&clientcmd.ConfigOverrides{})\n\t\trawconf, err := clientConfig.RawConfig()\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tconfig = rawconf.DeepCopy()\n\t})\n\n\tJustBeforeEach(func() {\n\t\tf, err := os.CreateTemp(\"\", \"kubeconfig\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tbuf, err := runtime.Encode(clientcmdlatest.Codec, config)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t_, err = f.Write(buf)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\terr = f.Close()\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tkubeconfigFile = f.Name()\n\t\targs = append(args, \"--kubeconfig\", kubeconfigFile)\n\t})\n\tAfterEach(func() {\n\t\tos.Remove(kubeconfigFile)\n\t\tcancel()\n\t})\n\n\tBeforeEach(func() {\n\t\tc = corev1.NewForConfigOrDie(clusterConfigOrDie())\n\n\t\tinput = &v1.Secret{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tNamespace: ns,\n\t\t\t\tName:      secretName,\n\t\t\t},\n\t\t\tData: map[string][]byte{\n\t\t\t\t\"foo\": []byte(\"bar\"),\n\t\t\t},\n\t\t}\n\n\t\tvar err error\n\t\tprivKeys, certs, err = fetchKeys(ctx, c)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t})\n\n\tJustBeforeEach(func() {\n\t\toutobj, err := runKubesealWith(args, input)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tss = outobj.(*ssv1alpha1.SealedSecret)\n\t})\n\n\tContext(\"Without args\", func() {\n\t\tconst testNs = \"testns\"\n\t\tBeforeEach(func() {\n\t\t\tinput.Namespace = testNs\n\t\t})\n\n\t\tIt(\"should have the right objectmeta\", func() {\n\t\t\tExpect(ss.Kind).To(Equal(\"SealedSecret\"))\n\t\t\tExpect(ss.GetName()).To(Equal(secretName))\n\t\t\tExpect(ss.GetNamespace()).To(Equal(testNs))\n\t\t})\n\n\t\tIt(\"should contain the right value\", func() {\n\t\t\ts, err := ss.Unseal(scheme.Codecs, privKeys)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(s.Data).To(HaveKeyWithValue(\"foo\", []byte(\"bar\")))\n\t\t})\n\t})\n\n\tContext(\"No input namespace\", func() {\n\t\tconst testNs = \"nons\"\n\n\t\tBeforeEach(func() {\n\t\t\t// set kubeconfig default namespace to testNs\n\t\t\tconfig.Contexts[config.CurrentContext].Namespace = testNs\n\t\t})\n\n\t\tIt(\"should use namespace from kubeconfig\", func() {\n\t\t\tExpect(ss.GetNamespace()).To(Equal(testNs))\n\t\t})\n\n\t\tIt(\"should qualify the Secret\", func() {\n\t\t\ts, err := ss.Unseal(scheme.Codecs, privKeys)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(s.GetNamespace()).To(Equal(testNs))\n\t\t})\n\t})\n\n\tContext(\"With --namespace\", func() {\n\t\tconst testNs = \"argns\"\n\t\tBeforeEach(func() {\n\t\t\targs = append(args, \"-n\", testNs)\n\t\t})\n\n\t\tIt(\"should qualify the output SealedSecret\", func() {\n\t\t\tExpect(ss.GetNamespace()).To(Equal(testNs))\n\t\t})\n\n\t\tIt(\"should qualify the Secret\", func() {\n\t\t\ts, err := ss.Unseal(scheme.Codecs, privKeys)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(s.GetNamespace()).To(Equal(testNs))\n\t\t})\n\t})\n\n\tContext(\"Offline, with --cert\", func() {\n\t\tvar certfile *os.File\n\n\t\tBeforeEach(func() {\n\t\t\t// Invalidate address of current cluster\n\t\t\tcluster := config.Contexts[config.CurrentContext].Cluster\n\t\t\tconfig.Clusters[cluster].Server = \"http://0.0.0.0:1\"\n\t\t})\n\n\t\tBeforeEach(func() {\n\t\t\tvar err error\n\t\t\tcertfile, err = os.CreateTemp(\"\", \"kubeseal-test\")\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\t\tfor _, cert := range certs {\n\t\t\t\tcertfile.Write(pem.EncodeToMemory(&pem.Block{Type: certUtil.CertificateBlockType, Bytes: cert.Raw}))\n\t\t\t}\n\t\t\tcertfile.Close()\n\n\t\t\targs = append(args, \"--cert\", certfile.Name())\n\t\t})\n\t\tAfterEach(func() {\n\t\t\tif certfile != nil {\n\t\t\t\tos.Remove(certfile.Name())\n\t\t\t\tcertfile = nil\n\t\t\t}\n\t\t})\n\n\t\tIt(\"should output the right value\", func() {\n\t\t\ts, err := ss.Unseal(scheme.Codecs, privKeys)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(s.Data).To(HaveKeyWithValue(\"foo\", []byte(\"bar\")))\n\t\t})\n\t})\n})\n\nvar _ = Describe(\"kubeseal (with invalid input)\", func() {\n\tvar input io.Reader\n\tvar output *bytes.Buffer\n\tvar args []string\n\n\tBeforeEach(func() {\n\t\toutput = &bytes.Buffer{}\n\t})\n\n\tIt(\"should throw an error\", func() {\n\t\terr := runKubeseal(args, input, output)\n\t\tExpect(err).To(HaveOccurred())\n\t})\n})\n\nvar _ = Describe(\"kubeseal --fetch-cert\", func() {\n\tvar c corev1.CoreV1Interface\n\tvar input io.Reader\n\tvar output *bytes.Buffer\n\tvar args []string\n\tvar (\n\t\tctx    context.Context\n\t\tcancel context.CancelFunc\n\t)\n\n\tBeforeEach(func() {\n\t\tctx, cancel = context.WithCancel(context.Background())\n\t\tc = corev1.NewForConfigOrDie(clusterConfigOrDie())\n\n\t\targs = append(args, \"--fetch-cert\")\n\t\toutput = &bytes.Buffer{}\n\t})\n\tJustBeforeEach(func() {\n\t\terr := runKubeseal(args, input, output)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t})\n\tAfterEach(func() {\n\t\tcancel()\n\t})\n\n\tIt(\"should produce the certificate\", func() {\n\t\t_, certs, err := fetchKeys(ctx, c)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tExpect(certUtil.ParseCertsPEM(output.Bytes())).\n\t\t\tShould(Equal(certs))\n\t})\n})\n\nvar _ = Describe(\"kubeseal --version\", func() {\n\tvar input io.Reader\n\tvar output *bytes.Buffer\n\tvar args []string\n\n\tBeforeEach(func() {\n\t\targs = []string{\"--version\"}\n\t\toutput = &bytes.Buffer{}\n\t})\n\n\tJustBeforeEach(func() {\n\t\terr := runKubeseal(args, input, output)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t})\n\n\tIt(\"should produce the version\", func() {\n\t\tExpect(output.String()).Should(MatchRegexp(\"^kubeseal version: (v[0-9]+\\\\.[0-9]+\\\\.[0-9]+|[0-9a-f]{40})(\\\\+dirty)?\"))\n\t})\n})\n\nvar _ = Describe(\"kubeseal --verify\", func() {\n\tconst secretName = \"testSecret\"\n\tconst testNs = \"testverifyns\"\n\tvar input io.Reader\n\tvar output *bytes.Buffer\n\tvar ss *ssv1alpha1.SealedSecret\n\tvar args []string\n\tvar err error\n\n\tBeforeEach(func() {\n\t\targs = append(args, \"--validate\")\n\t\toutput = &bytes.Buffer{}\n\t})\n\n\tBeforeEach(func() {\n\t\tinput := &v1.Secret{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tNamespace: testNs,\n\t\t\t\tName:      secretName,\n\t\t\t},\n\t\t\tData: map[string][]byte{\n\t\t\t\t\"foo\": []byte(\"bar\"),\n\t\t\t},\n\t\t}\n\t\toutobj, err := runKubesealWith([]string{}, input)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tss = outobj.(*ssv1alpha1.SealedSecret)\n\t})\n\n\tJustBeforeEach(func() {\n\t\tenc := scheme.Codecs.LegacyCodec(ssv1alpha1.SchemeGroupVersion)\n\t\tindata, err := runtime.Encode(enc, ss)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tinput = bytes.NewReader(indata)\n\t})\n\n\tJustBeforeEach(func() {\n\t\terr = runKubeseal(args, input, output)\n\t})\n\n\tContext(\"valid sealed secret\", func() {\n\t\tIt(\"should see the sealed secret as valid\", func() {\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t})\n\t})\n\n\tContext(\"invalid sealed secret\", func() {\n\t\tBeforeEach(func() {\n\t\t\tss.Name = \"a-completely-different-name\"\n\t\t})\n\n\t\tIt(\"should see the sealed secret as invalid\", func() {\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t})\n\t})\n\n})\n\nvar _ = Describe(\"kubeseal --cert\", func() {\n\tvar input io.Reader\n\tvar output *bytes.Buffer\n\tvar args []string\n\n\tBeforeEach(func() {\n\t\targs = []string{\"--cert\", \"/?this/file/cannot/possibly/exist/right?\"}\n\t\toutput = &bytes.Buffer{}\n\t})\n\n\tJustBeforeEach(func() {\n\t\terr := runKubeseal(args, input, io.Discard, runAppWithStderr(output))\n\t\tExpect(err).To(HaveOccurred())\n\t})\n\n\tIt(\"should return an error\", func() {\n\t\tExpect(output.String()).Should(MatchRegexp(\"^error:.*no such file or directory\"))\n\t})\n})\n\nvar _ = Describe(\"kubeseal --recovery-unseal\", func() {\n\tconst ns = \"default\"\n\tconst secretName = \"testSecret\"\n\n\tvar args []string\n\tvar backupKeysFile *os.File\n\tvar c corev1.CoreV1Interface\n\tvar err error\n\tvar sealedSecretInput []byte\n\tvar ss *ssv1alpha1.SealedSecret\n\tvar stderr *bytes.Buffer\n\tvar stdout *bytes.Buffer\n\tvar (\n\t\tctx    context.Context\n\t\tcancel context.CancelFunc\n\t)\n\n\tBeforeEach(func() {\n\t\tctx, cancel = context.WithCancel(context.Background())\n\t\tc = corev1.NewForConfigOrDie(clusterConfigOrDie())\n\n\t\tinput := &v1.Secret{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tNamespace: ns,\n\t\t\t\tName:      secretName,\n\t\t\t},\n\t\t\tData: map[string][]byte{\n\t\t\t\t\"foo\": []byte(\"bar\"),\n\t\t\t},\n\t\t}\n\t\toutobj, err := runKubesealWith([]string{}, input)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tss = outobj.(*ssv1alpha1.SealedSecret)\n\n\t\tenc := scheme.Codecs.LegacyCodec(ssv1alpha1.SchemeGroupVersion)\n\t\tsealedSecretInput, err = runtime.Encode(enc, ss)\n\t\tExpect(err).NotTo(HaveOccurred())\n\t})\n\tBeforeEach(func() {\n\t\tkey, err := c.Secrets(*controllerNs).List(ctx, metav1.ListOptions{\n\t\t\tLabelSelector: keySelector,\n\t\t})\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tbackupKeysFile, err = os.CreateTemp(\"\", \"key\")\n\t\tExpect(err).NotTo(HaveOccurred())\n\t\tdefer backupKeysFile.Close()\n\n\t\tjson, err := json.Marshal(key)\n\t\tExpect(err).NotTo(HaveOccurred())\n\n\t\tbackupKeysFile.Write(json)\n\t})\n\n\tBeforeEach(func() {\n\t\targs = []string{\"--recovery-unseal\", \"--kubeconfig\", \"/?this/file/cannot/possibly/exist/right?\"}\n\t\tstderr = &bytes.Buffer{}\n\t\tstdout = &bytes.Buffer{}\n\t})\n\n\tJustBeforeEach(func() {\n\t\terr = runKubeseal(args, bytes.NewReader(sealedSecretInput), stdout, runAppWithStderr(stderr))\n\t})\n\tAfterEach(func() {\n\t\tcancel()\n\t})\n\n\tContext(\"without --recovery-private-key\", func() {\n\t\tIt(\"should return an error\", func() {\n\t\t\tExpect(err).To(HaveOccurred())\n\t\t\tExpect(stderr.String()).Should(MatchRegexp(\"^error:.*key could decrypt secret (.*)\"))\n\t\t})\n\t})\n\n\tContext(\"with valid --recovery-private-key\", func() {\n\t\tvar secret v1.Secret\n\n\t\tBeforeEach(func() {\n\t\t\targs = append(args, \"--recovery-private-key\", backupKeysFile.Name())\n\t\t})\n\n\t\tIt(\"should successfully unseal the secret\", func() {\n\t\t\tjson.Unmarshal(stdout.Bytes(), &secret)\n\t\t\tExpect(err).NotTo(HaveOccurred())\n\t\t\tExpect(secret.Data).To(HaveKeyWithValue(\"foo\", []byte(\"bar\")))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "jsonnetfile.json",
    "content": "{\n    \"dependencies\": [\n        {\n            \"name\": \"kube-libsonnet\",\n            \"source\": {\n                \"git\": {\n                    \"remote\": \"https://github.com/bitnami-labs/kube-libsonnet\",\n                    \"subdir\": \"\"\n                }\n            },\n            \"version\": \"master\"\n        }\n    ]\n}\n"
  },
  {
    "path": "jsonnetfile.lock.json",
    "content": "{\n    \"dependencies\": [\n        {\n            \"name\": \"kube-libsonnet\",\n            \"source\": {\n                \"git\": {\n                    \"remote\": \"https://github.com/bitnami-labs/kube-libsonnet\",\n                    \"subdir\": \"\"\n                }\n            },\n            \"version\": \"7df1459e6d890d54eb96ea3df70d7c84b8b3fb0e\"\n        }\n    ]\n}\n"
  },
  {
    "path": "kube-fixes.libsonnet",
    "content": "{\n  CustomResourceDefinition(group, version, kind): {\n    local this = self,\n    apiVersion: 'apiextensions.k8s.io/v1',\n    kind: 'CustomResourceDefinition',\n    metadata+: {\n      name: this.spec.names.plural + '.' + this.spec.group,\n    },\n    spec: {\n      scope: 'Namespaced',\n      group: group,\n      versions_:: {\n        [version]: {\n          served: true,\n          storage: true,\n        },\n      },\n      versions: $.mapToNamedList(self.versions_),\n      names: {\n        kind: kind,\n        singular: $.toLower(self.kind),\n        plural: self.singular + 's',\n        listKind: self.kind + 'List',\n      },\n    },\n  },\n}\n"
  },
  {
    "path": "pkg/apis/sealedsecrets/v1alpha1/doc.go",
    "content": "// +k8s:deepcopy-gen=package,register\n\n// +groupName=bitnami.com\n\n// Package v1alpha1 contains the definition of the sealed-secrets v1alpha1 API. Some of the code in this package is generated.\npackage v1alpha1\n"
  },
  {
    "path": "pkg/apis/sealedsecrets/v1alpha1/register.go",
    "content": "package v1alpha1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n)\n\n// GroupName is the group name used in this package.\nconst GroupName = \"bitnami.com\"\n\nvar (\n\t// SchemeGroupVersion is the group version used to register these objects.\n\tSchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: \"v1alpha1\"}\n\n\t// SchemeBuilder adds this group to scheme.\n\tSchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)\n\t// AddToScheme is a global function that registers this API group & version to a scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n\nfunc init() {\n\tutilruntime.Must(SchemeBuilder.AddToScheme(scheme.Scheme))\n}\n\n// Resource takes an unqualified resource and returns a Group qualified GroupResource.\nfunc Resource(resource string) schema.GroupResource {\n\treturn SchemeGroupVersion.WithResource(resource).GroupResource()\n}\n\nfunc addKnownTypes(scheme *runtime.Scheme) error {\n\tscheme.AddKnownTypes(SchemeGroupVersion,\n\t\t&SealedSecret{},\n\t\t&SealedSecretList{},\n\t)\n\tmetav1.AddToGroupVersion(scheme, SchemeGroupVersion)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/apis/sealedsecrets/v1alpha1/sealedsecret_expansion.go",
    "content": "package v1alpha1\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"text/template\"\n\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\truntimeserializer \"k8s.io/apimachinery/pkg/runtime/serializer\"\n\n\t\"github.com/Masterminds/sprig/v3\"\n\t\"github.com/mkmik/multierror\"\n\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/crypto\"\n)\n\nconst (\n\t// The StrictScope pins the sealed secret to a specific namespace and a specific name.\n\tStrictScope SealingScope = iota\n\t// The NamespaceWideScope only pins a sealed secret to a specific namespace.\n\tNamespaceWideScope\n\t// The ClusterWideScope allows the sealed secret to be unsealed in any namespace of the cluster.\n\tClusterWideScope\n\n\t// The DefaultScope is currently the StrictScope.\n\tDefaultScope = StrictScope\n)\n\nvar (\n\t// TODO(mkm): remove after a release.\n\tAcceptDeprecatedV1Data = false\n\n\tsprigFuncMap = sprig.GenericFuncMap() // a singleton for better performance\n)\n\nfunc init() {\n\t// Avoid allowing the user to learn things about the environment.\n\tdelete(sprigFuncMap, \"env\")\n\tdelete(sprigFuncMap, \"expandenv\")\n\tdelete(sprigFuncMap, \"getHostByName\")\n}\n\n// SealedSecretExpansion has methods to work with SealedSecrets resources.\ntype SealedSecretExpansion interface {\n\tUnseal(codecs runtimeserializer.CodecFactory, privKeys map[string]*rsa.PrivateKey) (*v1.Secret, error)\n}\n\n// SealingScope is an enum that declares the mobility of a sealed secret by defining\n// in which scopes.\ntype SealingScope int\n\nfunc (s *SealingScope) String() string {\n\tswitch *s {\n\tcase StrictScope:\n\t\treturn \"strict\"\n\tcase NamespaceWideScope:\n\t\treturn \"namespace-wide\"\n\tcase ClusterWideScope:\n\t\treturn \"cluster-wide\"\n\tdefault:\n\t\treturn fmt.Sprintf(\"undefined-%d\", *s)\n\t}\n}\n\nfunc (s *SealingScope) Set(v string) error {\n\tswitch v {\n\tcase \"\":\n\t\t*s = DefaultScope\n\tcase \"strict\":\n\t\t*s = StrictScope\n\tcase \"namespace-wide\":\n\t\t*s = NamespaceWideScope\n\tcase \"cluster-wide\":\n\t\t*s = ClusterWideScope\n\tdefault:\n\t\treturn fmt.Errorf(\"must be one of: strict, namespace-wide, cluster-wide\")\n\t}\n\treturn nil\n}\n\n// Type implements the pflag.Value interface.\nfunc (s *SealingScope) Type() string { return \"string\" }\n\n// EncryptionLabel returns the label meant to be used for encrypting a sealed secret according to scope.\nfunc EncryptionLabel(namespace, name string, scope SealingScope) []byte {\n\tvar l string\n\tswitch scope {\n\tcase ClusterWideScope:\n\t\tl = \"\"\n\tcase NamespaceWideScope:\n\t\tl = namespace\n\tcase StrictScope:\n\t\tfallthrough\n\tdefault:\n\t\tl = fmt.Sprintf(\"%s/%s\", namespace, name)\n\t}\n\treturn []byte(l)\n}\n\n// Returns labels followed by clusterWide followed by namespaceWide.\nfunc labelFor(o metav1.Object) []byte {\n\treturn EncryptionLabel(o.GetNamespace(), o.GetName(), SecretScope(o))\n}\n\n// SecretScope returns the scope of a secret to be sealed, as annotated in its metadata.\nfunc SecretScope(o metav1.Object) SealingScope {\n\tif o.GetAnnotations()[SealedSecretClusterWideAnnotation] == \"true\" {\n\t\treturn ClusterWideScope\n\t}\n\tif o.GetAnnotations()[SealedSecretNamespaceWideAnnotation] == \"true\" {\n\t\treturn NamespaceWideScope\n\t}\n\treturn StrictScope\n}\n\n// Scope returns the scope of the sealed secret, as annotated in its metadata.\nfunc (s *SealedSecret) Scope() SealingScope {\n\treturn SecretScope(&s.Spec.Template)\n}\n\n// NewSealedSecretV1 creates a new SealedSecret object wrapping the\n// provided secret. This encrypts all the secrets into a single encrypted\n// blob and stores it in the `Data` attribute. Keeping this for backward\n// compatibility.\nfunc NewSealedSecretV1(codecs runtimeserializer.CodecFactory, pubKey *rsa.PublicKey, secret *v1.Secret) (*SealedSecret, error) {\n\tinfo, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), runtime.ContentTypeJSON)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"binary can't serialize JSON\")\n\t}\n\n\tif SecretScope(secret) != ClusterWideScope && secret.GetNamespace() == \"\" {\n\t\treturn nil, fmt.Errorf(\"secret must declare a namespace\")\n\t}\n\n\tcodec := codecs.EncoderForVersion(info.Serializer, v1.SchemeGroupVersion)\n\tplaintext, err := runtime.Encode(codec, secret)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// RSA-OAEP will fail to decrypt unless the same label is used\n\t// during decryption.\n\tlabel := labelFor(secret)\n\n\tciphertext, err := crypto.HybridEncrypt(rand.Reader, pubKey, plaintext, label)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ts := &SealedSecret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      secret.GetName(),\n\t\t\tNamespace: secret.GetNamespace(),\n\t\t},\n\t\tSpec: SealedSecretSpec{\n\t\t\tData: ciphertext,\n\t\t},\n\t}\n\n\ts.Annotations = UpdateScopeAnnotations(s.Annotations, SecretScope(secret))\n\n\treturn s, nil\n}\n\n// UpdateScopeAnnotations updates the annotation map so that it reflects the desired scope.\n// It does so by updating and/or deleting existing annotations.\nfunc UpdateScopeAnnotations(anno map[string]string, scope SealingScope) map[string]string {\n\tif anno == nil {\n\t\tanno = map[string]string{}\n\t}\n\tdelete(anno, SealedSecretNamespaceWideAnnotation)\n\tdelete(anno, SealedSecretClusterWideAnnotation)\n\n\tif scope == NamespaceWideScope {\n\t\tanno[SealedSecretNamespaceWideAnnotation] = \"true\"\n\t}\n\tif scope == ClusterWideScope {\n\t\tanno[SealedSecretClusterWideAnnotation] = \"true\"\n\t}\n\treturn anno\n}\n\n// StripLastAppliedAnnotations strips annotations added by tools such as kubectl and kubecfg\n// that contain a full copy of the original object kept in the annotation for strategic-merge-patch\n// purposes. We need to remove these annotations when sealing an existing secret otherwise we'd leak\n// the secrets.\nfunc StripLastAppliedAnnotations(annotations map[string]string) {\n\tif annotations == nil {\n\t\treturn\n\t}\n\tkeys := []string{\n\t\t\"kubectl.kubernetes.io/last-applied-configuration\",\n\t\t\"kubecfg.ksonnet.io/last-applied-configuration\",\n\t}\n\tfor _, k := range keys {\n\t\tdelete(annotations, k)\n\t}\n}\n\n// NewSealedSecret creates a new SealedSecret object wrapping the\n// provided secret. This encrypts only the values of each secrets\n// individually, so secrets can be updated one by one.\nfunc NewSealedSecret(codecs runtimeserializer.CodecFactory, pubKey *rsa.PublicKey, secret *v1.Secret) (*SealedSecret, error) {\n\tif SecretScope(secret) != ClusterWideScope && secret.GetNamespace() == \"\" {\n\t\treturn nil, fmt.Errorf(\"secret must declare a namespace\")\n\t}\n\n\ts := &SealedSecret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      secret.GetName(),\n\t\t\tNamespace: secret.GetNamespace(),\n\t\t},\n\t\tSpec: SealedSecretSpec{\n\t\t\tTemplate: SecretTemplateSpec{\n\t\t\t\t// ObjectMeta copied below\n\t\t\t\tType:      secret.Type,\n\t\t\t\tImmutable: secret.Immutable,\n\t\t\t},\n\t\t\tEncryptedData: map[string]string{},\n\t\t},\n\t}\n\tsecret.ObjectMeta.DeepCopyInto(&s.Spec.Template.ObjectMeta)\n\n\t// the input secret could come from a real secret object applied with `kubectl apply` or similar tools\n\t// which put a copy of the object version at application time in an annotation in order to support\n\t// strategic merge patch in subsequent updates. We need to strip those annotations or else we would\n\t// be leaking secrets in clear in a way that might be non obvious to users.\n\t// See https://github.com/bitnami-labs/sealed-secrets/issues/227\n\tStripLastAppliedAnnotations(s.Spec.Template.ObjectMeta.Annotations)\n\n\t// Cleanup ownerReference (See #243)\n\ts.Spec.Template.ObjectMeta.OwnerReferences = nil\n\n\t// RSA-OAEP will fail to decrypt unless the same label is used\n\t// during decryption.\n\tlabel := labelFor(secret)\n\n\tfor key, value := range secret.Data {\n\t\tciphertext, err := crypto.HybridEncrypt(rand.Reader, pubKey, value, label)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ts.Spec.EncryptedData[key] = base64.StdEncoding.EncodeToString(ciphertext)\n\t}\n\n\tfor key, value := range secret.StringData {\n\t\tciphertext, err := crypto.HybridEncrypt(rand.Reader, pubKey, []byte(value), label)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ts.Spec.EncryptedData[key] = base64.StdEncoding.EncodeToString(ciphertext)\n\t}\n\n\ts.Annotations = UpdateScopeAnnotations(s.Annotations, SecretScope(secret))\n\n\treturn s, nil\n}\n\n// Unseal decrypts and returns the embedded v1.Secret.\nfunc (s *SealedSecret) Unseal(codecs runtimeserializer.CodecFactory, privKeys map[string]*rsa.PrivateKey) (*v1.Secret, error) {\n\tboolTrue := true\n\tsmeta := s.GetObjectMeta()\n\n\t// This will fail to decrypt unless the same label was used\n\t// during encryption.  This check ensures that we can't be\n\t// tricked into decrypting a sealed secret into an unexpected\n\t// namespace/name.\n\tlabel := labelFor(smeta)\n\n\tvar secret v1.Secret\n\n\tif s.Spec.Data == nil {\n\t\ts.Spec.Template.ObjectMeta.DeepCopyInto(&secret.ObjectMeta)\n\t\tsecret.Type = s.Spec.Template.Type\n\t\tsecret.Immutable = s.Spec.Template.Immutable\n\n\t\tsecret.Data = map[string][]byte{}\n\t\tdata := map[string]string{}\n\n\t\tvar errs []error\n\t\tfor key, value := range s.Spec.EncryptedData {\n\t\t\tvalueBytes, err := base64.StdEncoding.DecodeString(value)\n\t\t\tif err != nil {\n\t\t\t\terrs = append(errs, multierror.Tag(key, err))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tplaintext, err := crypto.HybridDecrypt(rand.Reader, privKeys, valueBytes, label)\n\t\t\tif err != nil {\n\t\t\t\terrs = append(errs, multierror.Tag(key, err))\n\t\t\t}\n\t\t\tsecret.Data[key] = plaintext\n\t\t\tdata[key] = string(plaintext)\n\t\t}\n\n\t\tfor key, value := range s.Spec.Template.Data {\n\t\t\tvar plaintext bytes.Buffer\n\n\t\t\ttemplate, err := template.New(key).Funcs(sprigFuncMap).Parse(value)\n\t\t\tif err != nil {\n\t\t\t\terrs = append(errs, multierror.Tag(key, err))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\terr = template.Execute(&plaintext, data)\n\t\t\tif err != nil {\n\t\t\t\terrs = append(errs, multierror.Tag(key, err))\n\t\t\t}\n\t\t\tsecret.Data[key] = plaintext.Bytes()\n\t\t}\n\n\t\tif errs != nil {\n\t\t\treturn nil, multierror.Format(errors.Join(multierror.Uniq(errs)...), multierror.InlineFormatter)\n\t\t}\n\t} else if AcceptDeprecatedV1Data { // Support decrypting old secrets for backward compatibility\n\t\tif len(s.Spec.EncryptedData) > 0 {\n\t\t\treturn nil, fmt.Errorf(\"cannot use the field 'encryptedData' and the deprecated field 'data' at the same time\")\n\t\t}\n\n\t\tplaintext, err := crypto.HybridDecrypt(rand.Reader, privKeys, s.Spec.Data, label)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tdec := codecs.UniversalDecoder(secret.GroupVersionKind().GroupVersion())\n\t\tif err = runtime.DecodeInto(dec, plaintext, &secret); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\treturn nil, fmt.Errorf(\"using deprecated 'data' field, use 'encryptedData' or flip the feature flag\")\n\t}\n\n\t// Ensure these are set to what we expect\n\tsecret.SetNamespace(smeta.GetNamespace())\n\tsecret.SetName(smeta.GetName())\n\n\tgvk := s.GetObjectKind().GroupVersionKind()\n\tif anno, ok := s.Spec.Template.Annotations[SealedSecretSkipSetOwnerReferencesAnnotation]; !ok || anno != \"true\" {\n\t\t// Refer back to owning SealedSecret\n\t\townerRefs := []metav1.OwnerReference{\n\t\t\t{\n\t\t\t\tAPIVersion: gvk.GroupVersion().String(),\n\t\t\t\tKind:       gvk.Kind,\n\t\t\t\tName:       smeta.GetName(),\n\t\t\t\tUID:        smeta.GetUID(),\n\t\t\t\tController: &boolTrue,\n\t\t\t},\n\t\t}\n\t\tsecret.SetOwnerReferences(ownerRefs)\n\t}\n\n\treturn &secret, nil\n}\n"
  },
  {
    "path": "pkg/apis/sealedsecrets/v1alpha1/sealedsecret_test.go",
    "content": "package v1alpha1\n\nimport (\n\t\"bytes\"\n\t\"crypto/rsa\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"io\"\n\tmathrand \"math/rand\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/serializer\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/crypto\"\n\n\t// Install standard API types.\n\t_ \"k8s.io/client-go/kubernetes\"\n)\n\nvar _ runtime.Object = &SealedSecret{}\nvar _ metav1.ObjectMetaAccessor = &SealedSecret{}\nvar _ runtime.Object = &SealedSecretList{}\nvar _ metav1.ListMetaAccessor = &SealedSecretList{}\n\nfunc TestSealingScope(t *testing.T) {\n\ttestCases := []struct {\n\t\tscope SealingScope\n\t\tname  string\n\t}{\n\t\t{StrictScope, \"strict\"},\n\t\t{NamespaceWideScope, \"namespace-wide\"},\n\t\t{ClusterWideScope, \"cluster-wide\"},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tif got, want := tc.scope.String(), tc.name; got != want {\n\t\t\tt.Errorf(\"got: %q, want: %q\", got, want)\n\t\t}\n\n\t\tvar s SealingScope\n\t\terr := s.Set(tc.name)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif got, want := s, tc.scope; got != want {\n\t\t\tt.Errorf(\"got: %d, want: %d\", got, want)\n\t\t}\n\t}\n\n\tvar s SealingScope\n\terr := s.Set(\"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif got, want := s, StrictScope; got != want {\n\t\tt.Errorf(\"got: %d, want: %d\", got, want)\n\t}\n}\n\nfunc TestEncryptionLabel(t *testing.T) {\n\tconst (\n\t\tns   = \"myns\"\n\t\tname = \"myname\"\n\t)\n\ttestCases := []struct {\n\t\tscope SealingScope\n\t\tlabel string\n\t}{\n\t\t{StrictScope, \"myns/myname\"},\n\t\t{NamespaceWideScope, \"myns\"},\n\t\t{ClusterWideScope, \"\"},\n\t}\n\tfor _, tc := range testCases {\n\t\tif got, want := string(EncryptionLabel(ns, name, tc.scope)), tc.label; got != want {\n\t\t\tt.Errorf(\"got: %q, want: %q\", got, want)\n\t\t}\n\t}\n}\n\nfunc TestLabel(t *testing.T) {\n\ts := v1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"myname\",\n\t\t\tNamespace: \"myns\",\n\t\t},\n\t}\n\tl := labelFor(&s)\n\tif string(l) != \"myns/myname\" {\n\t\tt.Errorf(\"Unexpected label: %#v\", l)\n\t}\n}\n\nfunc TestClusterWide(t *testing.T) {\n\ts := v1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"myname\",\n\t\t\tNamespace: \"myns\",\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tSealedSecretClusterWideAnnotation: \"true\",\n\t\t\t},\n\t\t},\n\t}\n\tl := labelFor(&s)\n\tif string(l) != \"\" {\n\t\tt.Errorf(\"Unexpected label: %#v\", l)\n\t}\n}\n\nfunc TestNamespaceWide(t *testing.T) {\n\ts := v1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"myname\",\n\t\t\tNamespace: \"myns\",\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tSealedSecretNamespaceWideAnnotation: \"true\",\n\t\t\t},\n\t\t},\n\t}\n\tl := labelFor(&s)\n\tif string(l) != \"myns\" {\n\t\tt.Errorf(\"Unexpected label: %#v\", l)\n\t}\n}\n\nfunc TestClusterAndNamespaceWide(t *testing.T) {\n\ts := v1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"myname\",\n\t\t\tNamespace: \"myns\",\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tSealedSecretNamespaceWideAnnotation: \"true\",\n\t\t\t\tSealedSecretClusterWideAnnotation:   \"true\",\n\t\t\t},\n\t\t},\n\t}\n\tl := labelFor(&s)\n\tif string(l) != \"\" {\n\t\tt.Errorf(\"Unexpected label: %#v\", l)\n\t}\n}\n\nfunc TestSerialize(t *testing.T) {\n\ts := SealedSecret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"myname\",\n\t\t\tNamespace: \"myns\",\n\t\t},\n\t\tSpec: SealedSecretSpec{\n\t\t\tEncryptedData: map[string]string{\n\t\t\t\t\"foo\": base64.StdEncoding.EncodeToString([]byte(\"secret1\")),\n\t\t\t\t\"bar\": base64.StdEncoding.EncodeToString([]byte(\"secret2\")),\n\t\t\t},\n\t\t},\n\t}\n\n\tinfo, ok := runtime.SerializerInfoForMediaType(scheme.Codecs.SupportedMediaTypes(), runtime.ContentTypeJSON)\n\tif !ok {\n\t\tt.Fatalf(\"binary can't serialize JSON\")\n\t}\n\n\tenc := scheme.Codecs.EncoderForVersion(info.Serializer, SchemeGroupVersion)\n\tbuf := bytes.Buffer{}\n\tif err := enc.Encode(&s, &buf); err != nil {\n\t\tt.Errorf(\"Error encoding: %v\", err)\n\t}\n\n\tt.Logf(\"text is %s\", buf.String())\n}\n\n// This is omg-not safe for real crypto use!\nfunc testRand() io.Reader {\n\treturn mathrand.New(mathrand.NewSource(42))\n}\n\nfunc generateTestKey(t *testing.T, rand io.Reader, bits int) (*rsa.PrivateKey, map[string]*rsa.PrivateKey) {\n\tkey, err := rsa.GenerateKey(rand, 2048)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to generate test key: %v\", err)\n\t}\n\tfingerprint, err := crypto.PublicKeyFingerprint(&key.PublicKey)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to generate fingerprint: %v\", err)\n\t}\n\tkeys := map[string]*rsa.PrivateKey{fingerprint: key}\n\treturn key, keys\n}\n\nfunc TestSealRoundTrip(t *testing.T) {\n\tsecret := v1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"myname\",\n\t\t\tNamespace: \"myns\",\n\t\t},\n\t\tData: map[string][]byte{\n\t\t\t\"foo\": []byte(\"bar\"),\n\t\t},\n\t}\n\n\tssecret, codecs, keys := sealSecret(t, &secret, NewSealedSecret)\n\n\tsecret2, err := ssecret.Unseal(codecs, keys)\n\tif err != nil {\n\t\tt.Fatalf(\"Unseal returned error: %v\", err)\n\t}\n\n\tif !reflect.DeepEqual(secret.Data, secret2.Data) {\n\t\tt.Errorf(\"Unsealed secret != original secret: %v != %v\", secret, secret2)\n\t}\n}\n\nfunc TestSealRoundTripStringDataConversion(t *testing.T) {\n\tsecret := v1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"myname\",\n\t\t\tNamespace: \"myns\",\n\t\t},\n\t\tData: map[string][]byte{\n\t\t\t\"foo\": []byte(\"bar\"),\n\t\t\t\"fss\": []byte(\"brr\"),\n\t\t},\n\t\tStringData: map[string]string{\n\t\t\t\"fss\": \"baa\",\n\t\t},\n\t}\n\n\tunsealed := v1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"myname\",\n\t\t\tNamespace: \"myns\",\n\t\t},\n\t\tData: map[string][]byte{\n\t\t\t\"foo\": []byte(\"bar\"),\n\t\t\t\"fss\": []byte(\"baa\"),\n\t\t},\n\t}\n\n\tssecret, codecs, keys := sealSecret(t, &secret, NewSealedSecret)\n\n\tsecret2, err := ssecret.Unseal(codecs, keys)\n\tif err != nil {\n\t\tt.Fatalf(\"Unseal returned error: %v\", err)\n\t}\n\n\tif !reflect.DeepEqual(unsealed.Data, secret2.Data) {\n\t\tt.Errorf(\"Unsealed secret != original secret: %v != %v\", unsealed, secret2)\n\t}\n}\n\nfunc TestSealRoundTripWithClusterWide(t *testing.T) {\n\tsecret := v1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"myname\",\n\t\t\tNamespace: \"myns\",\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tSealedSecretClusterWideAnnotation: \"true\",\n\t\t\t},\n\t\t},\n\t\tData: map[string][]byte{\n\t\t\t\"foo\": []byte(\"bar\"),\n\t\t},\n\t}\n\n\tssecret, codecs, keys := sealSecret(t, &secret, NewSealedSecret)\n\n\tsecret2, err := ssecret.Unseal(codecs, keys)\n\tif err != nil {\n\t\tt.Fatalf(\"Unseal returned error: %v\", err)\n\t}\n\n\tif !reflect.DeepEqual(secret.Data, secret2.Data) {\n\t\tt.Errorf(\"Unsealed secret != original secret: %v != %v\", secret, secret2)\n\t}\n}\n\nfunc TestSealRoundTripWithMisMatchClusterWide(t *testing.T) {\n\tsecret := v1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"myname\",\n\t\t\tNamespace: \"myns\",\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tSealedSecretClusterWideAnnotation: \"true\",\n\t\t\t},\n\t\t},\n\t\tData: map[string][]byte{\n\t\t\t\"foo\": []byte(\"bar\"),\n\t\t},\n\t}\n\n\tssecret, codecs, keys := sealSecret(t, &secret, NewSealedSecret)\n\n\tssecret.ObjectMeta.Annotations[SealedSecretClusterWideAnnotation] = \"false\"\n\n\t_, err := ssecret.Unseal(codecs, keys)\n\tif err == nil {\n\t\tt.Fatal(\"Expecting error: got nil instead\")\n\t}\n}\n\nfunc TestSealRoundTripWithNamespaceWide(t *testing.T) {\n\tsecret := v1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"myname\",\n\t\t\tNamespace: \"myns\",\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tSealedSecretNamespaceWideAnnotation: \"true\",\n\t\t\t},\n\t\t},\n\t\tData: map[string][]byte{\n\t\t\t\"foo\": []byte(\"bar\"),\n\t\t},\n\t}\n\n\tssecret, codecs, keys := sealSecret(t, &secret, NewSealedSecret)\n\n\tsecret2, err := ssecret.Unseal(codecs, keys)\n\tif err != nil {\n\t\tt.Fatalf(\"Unseal returned error: %v\", err)\n\t}\n\n\tif !reflect.DeepEqual(secret.Data, secret2.Data) {\n\t\tt.Errorf(\"Unsealed secret != original secret: %v != %v\", secret, secret2)\n\t}\n}\n\nfunc TestSealRoundTripWithMisMatchNamespaceWide(t *testing.T) {\n\tsecret := v1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"myname\",\n\t\t\tNamespace: \"myns\",\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tSealedSecretNamespaceWideAnnotation: \"true\",\n\t\t\t},\n\t\t},\n\t\tData: map[string][]byte{\n\t\t\t\"foo\": []byte(\"bar\"),\n\t\t},\n\t}\n\n\tssecret, codecs, keys := sealSecret(t, &secret, NewSealedSecret)\n\n\tssecret.ObjectMeta.Annotations[SealedSecretNamespaceWideAnnotation] = \"false\"\n\n\t_, err := ssecret.Unseal(codecs, keys)\n\tif err == nil {\n\t\tt.Fatalf(\"Unseal did not return expected error: %v\", err)\n\t}\n}\n\nfunc TestSealRoundTripTemplateData(t *testing.T) {\n\tsecret := v1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"myname\",\n\t\t\tNamespace: \"myns\",\n\t\t},\n\t\tData: map[string][]byte{\n\t\t\t\"foo\":      []byte(\"bar\"),\n\t\t\t\"password\": []byte(\"hunter2'\\\"=\"),\n\t\t},\n\t}\n\n\tssecret, codecs, keys := sealSecret(t, &secret, NewSealedSecret)\n\n\tssecret.Spec.Template.Data = map[string]string{\n\t\t\"bar\":           `secret {{ index . \"foo\" }} !`,\n\t\t\"password-json\": `{{ toJson .password }}`,\n\t}\n\n\tsecret2, err := ssecret.Unseal(codecs, keys)\n\tif err != nil {\n\t\tt.Fatalf(\"Unseal returned error: %v\", err)\n\t}\n\n\tif got, want := string(secret2.Data[\"bar\"]), \"secret bar !\"; got != want {\n\t\tt.Errorf(\"got: %q, want: %q\", got, want)\n\t}\n\n\twant, err := json.Marshal(string(secret.Data[\"password\"]))\n\tif err != nil {\n\t\tt.Fatalf(\"json.Marshal returned error: %v\", err)\n\t}\n\n\tif got := string(secret2.Data[\"password-json\"]); got != string(want) {\n\t\tt.Errorf(\"got: %q, want: %q\", got, want)\n\t}\n}\n\nfunc TestTemplateWithoutEncryptedData(t *testing.T) {\n\tsealed := SealedSecret{\n\t\tSpec: SealedSecretSpec{\n\t\t\tTemplate: SecretTemplateSpec{\n\t\t\t\tData: map[string]string{\"foo\": \"bar\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tunsealed, err := sealed.Unseal(serializer.CodecFactory{}, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"Unseal returned error: %v\", err)\n\t}\n\n\tif got, want := unsealed.Data, map[string][]byte{\"foo\": []byte(\"bar\")}; !reflect.DeepEqual(got, want) {\n\t\tt.Errorf(\"got: %q, want: %q\", got, want)\n\t}\n}\n\nfunc TestSkipSetOwnerReference(t *testing.T) {\n\ttestCases := []struct {\n\t\tsealedSecret          SealedSecret\n\t\tskipSetOwnerReference bool\n\t\tsecret                v1.Secret\n\t}{\n\t\t{\n\t\t\tsealedSecret: SealedSecret{\n\t\t\t\tSpec: SealedSecretSpec{\n\t\t\t\t\tTemplate: SecretTemplateSpec{\n\t\t\t\t\t\tData: map[string]string{\"foo\": \"bar\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tskipSetOwnerReference: true,\n\t\t\tsecret: v1.Secret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsealedSecret: SealedSecret{\n\t\t\t\tSpec: SealedSecretSpec{\n\t\t\t\t\tTemplate: SecretTemplateSpec{\n\t\t\t\t\t\tData: map[string]string{\"foo\": \"bar\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tskipSetOwnerReference: false,\n\t\t\tsecret: v1.Secret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tOwnerReferences: []metav1.OwnerReference{},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tif tc.skipSetOwnerReference {\n\t\t\tif tc.sealedSecret.Spec.Template.Annotations == nil {\n\t\t\t\ttc.sealedSecret.Spec.Template.Annotations = make(map[string]string)\n\t\t\t}\n\t\t\ttc.sealedSecret.Spec.Template.Annotations[SealedSecretSkipSetOwnerReferencesAnnotation] = \"true\"\n\t\t}\n\t\tunsealed, err := tc.sealedSecret.Unseal(serializer.CodecFactory{}, nil)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unseal returned error: %v\", err)\n\t\t}\n\t\tif tc.sealedSecret.Spec.Template.Annotations[SealedSecretSkipSetOwnerReferencesAnnotation] == \"true\" &&\n\t\t\tlen(unsealed.ObjectMeta.OwnerReferences) > 0 {\n\t\t\tt.Errorf(\"got: owner, want: no owner\")\n\t\t} else if (tc.sealedSecret.Spec.Template.Annotations[SealedSecretSkipSetOwnerReferencesAnnotation] != \"true\") &&\n\t\t\tlen(unsealed.ObjectMeta.OwnerReferences) == 0 {\n\t\t\tt.Errorf(\"got: no owner, want:  owner\")\n\t\t}\n\t}\n}\n\nfunc TestSealMetadataPreservation(t *testing.T) {\n\tscheme := runtime.NewScheme()\n\tcodecs := serializer.NewCodecFactory(scheme)\n\n\tutilruntime.Must(SchemeBuilder.AddToScheme(scheme))\n\tutilruntime.Must(v1.SchemeBuilder.AddToScheme(scheme))\n\n\tkey, _ := generateTestKey(t, testRand(), 2048)\n\n\ttestCases := []struct {\n\t\tkey       string\n\t\tpreserved bool\n\t}{\n\t\t{\"foo\", true},\n\t\t{\"foo.bar.io/foo-bar-baz\", true},\n\t\t{\"kubectl.kubernetes.io/last-applied-configuration\", false},\n\t\t{\"kubecfg.ksonnet.io/last-applied-configuration\", false},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tsecret := v1.Secret{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"myname\",\n\t\t\t\tNamespace:   \"myns\",\n\t\t\t\tAnnotations: map[string]string{tc.key: \"test value\"},\n\t\t\t\tOwnerReferences: []metav1.OwnerReference{\n\t\t\t\t\t{\n\t\t\t\t\t\tAPIVersion: \"foo/v1\",\n\t\t\t\t\t\tKind:       \"Foo\",\n\t\t\t\t\t\tName:       \"foo\",\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\"foo\": []byte(\"bar\"),\n\t\t\t},\n\t\t}\n\n\t\tssecret, err := NewSealedSecret(codecs, &key.PublicKey, &secret)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"NewSealedSecret returned error: %v\", err)\n\t\t}\n\n\t\t_, got := ssecret.Spec.Template.Annotations[tc.key]\n\t\tif want := tc.preserved; got != want {\n\t\t\tt.Errorf(\"key %q: exists: %v, expected to exist: %v\", tc.key, got, want)\n\t\t}\n\n\t\tif got, want := len(ssecret.Spec.Template.OwnerReferences), 0; got != want {\n\t\t\tt.Errorf(\"got: %d, want: %d\", got, want)\n\t\t}\n\t}\n}\n\nfunc TestUnsealingV1Format(t *testing.T) {\n\tsecret := v1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"myname\",\n\t\t\tNamespace: \"myns\",\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tSealedSecretClusterWideAnnotation:   \"true\",\n\t\t\t\tSealedSecretNamespaceWideAnnotation: \"true\",\n\t\t\t},\n\t\t},\n\t\tData: map[string][]byte{\n\t\t\t\"foo\": []byte(\"bar\"),\n\t\t},\n\t}\n\n\tssecret, codecs, keys := sealSecret(t, &secret, NewSealedSecretV1)\n\n\tt.Run(\"AcceptDeprecatedV1Data\", testWithAcceptDeprecatedV1Data(true, func(t *testing.T) {\n\t\tsecret2, err := ssecret.Unseal(codecs, keys)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unseal returned error: %v\", err)\n\t\t}\n\n\t\tif !reflect.DeepEqual(secret.Data, secret2.Data) {\n\t\t\tt.Errorf(\"Unsealed secret != original secret: %v != %v\", secret, secret2)\n\t\t}\n\t}))\n\n\tt.Run(\"RejectDeprecatedV1Data\", testWithAcceptDeprecatedV1Data(false, func(t *testing.T) {\n\t\t_, err := ssecret.Unseal(codecs, keys)\n\t\tif needle := \"deprecated\"; err == nil || !strings.Contains(err.Error(), needle) {\n\t\t\tt.Fatalf(\"Expecting error: %v to contain %q\", err, needle)\n\t\t}\n\t}))\n}\n\nfunc TestRejectBothEncryptedDataAndDeprecatedV1Data(t *testing.T) {\n\tsecret := v1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"myname\",\n\t\t\tNamespace: \"myns\",\n\t\t},\n\t\tStringData: map[string]string{\"foo\": \"bar\"},\n\t}\n\n\tsealedSecret, codecs, keys := sealSecret(t, &secret, NewSealedSecret)\n\tsealedSecret.Spec.Data = []byte{}\n\n\tt.Run(\"AcceptDeprecatedV1Data\", testWithAcceptDeprecatedV1Data(true, func(t *testing.T) {\n\t\t_, err := sealedSecret.Unseal(codecs, keys)\n\t\tif needle := \"at the same time\"; err == nil || !strings.Contains(err.Error(), needle) {\n\t\t\tt.Fatalf(\"Expecting error: %v to contain %q\", err, needle)\n\t\t}\n\t}))\n\n\tt.Run(\"RejectDeprecatedV1Data\", testWithAcceptDeprecatedV1Data(false, func(t *testing.T) {\n\t\t_, err := sealedSecret.Unseal(codecs, keys)\n\t\tif needle := \"deprecated\"; err == nil || !strings.Contains(err.Error(), needle) {\n\t\t\tt.Fatalf(\"Expecting error: %v to contain %q\", err, needle)\n\t\t}\n\t}))\n}\n\nfunc TestInvalidBase64(t *testing.T) {\n\tsealedSecret := &SealedSecret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"myname\",\n\t\t\tNamespace: \"myns\",\n\t\t},\n\t\tSpec: SealedSecretSpec{\n\t\t\tEncryptedData: map[string]string{\n\t\t\t\t\"foo\": \"NOTVALIDBASE64\",\n\t\t\t},\n\t\t},\n\t}\n\n\tscheme := runtime.NewScheme()\n\tcodecs := serializer.NewCodecFactory(scheme)\n\t_, keys := generateTestKey(t, testRand(), 2048)\n\n\t_, err := sealedSecret.Unseal(codecs, keys)\n\tif err == nil {\n\t\tt.Fatal(\"Expecting error: got nil instead\")\n\t}\n\n\tif !strings.Contains(err.Error(), \"foo\") {\n\t\tt.Errorf(\"Expecting error: %q to contain field %q\", err, \"foo\")\n\t}\n\n\tif strings.Contains(err.Error(), \"decrypt\") {\n\t\tt.Errorf(\"Expecting error: %q to not contain %q (invalid base64 should skip decryption)\", err, \"decrypt\")\n\t}\n}\n\nfunc sealSecret(t *testing.T, secret *v1.Secret, newSealedSecret func(serializer.CodecFactory, *rsa.PublicKey, *v1.Secret) (*SealedSecret, error)) (*SealedSecret, serializer.CodecFactory, map[string]*rsa.PrivateKey) {\n\tscheme := runtime.NewScheme()\n\tcodecs := serializer.NewCodecFactory(scheme)\n\n\tutilruntime.Must(SchemeBuilder.AddToScheme(scheme))\n\tutilruntime.Must(v1.SchemeBuilder.AddToScheme(scheme))\n\n\tkey, keys := generateTestKey(t, testRand(), 2048)\n\n\tsealedSecret, err := newSealedSecret(codecs, &key.PublicKey, secret)\n\tif err != nil {\n\t\tt.Fatalf(\"NewSealedSecret returned error: %v\", err)\n\t}\n\n\treturn sealedSecret, codecs, keys\n}\n\nfunc testWithAcceptDeprecatedV1Data(acceptDeprecated bool, inner func(t *testing.T)) func(*testing.T) {\n\treturn func(t *testing.T) {\n\t\tdefer func(saved bool) {\n\t\t\tAcceptDeprecatedV1Data = saved\n\t\t}(AcceptDeprecatedV1Data)\n\t\tAcceptDeprecatedV1Data = acceptDeprecated\n\n\t\tinner(t)\n\t}\n}\n"
  },
  {
    "path": "pkg/apis/sealedsecrets/v1alpha1/types.go",
    "content": "package v1alpha1\n\nimport (\n\t\"encoding/json\"\n\n\tapiv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nconst (\n\t// SealedSecretName is the name used in SealedSecret CRD.\n\tSealedSecretName = \"sealed-secret.\" + GroupName\n\t// SealedSecretPlural is the collection plural used with SealedSecret API.\n\tSealedSecretPlural = \"sealedsecrets\"\n\n\t// Annotation namespace prefix.\n\tannoNs = \"sealedsecrets.\" + GroupName + \"/\"\n\n\t// SealedSecretClusterWideAnnotation is the name for the annotation for\n\t// setting the secret to be available cluster wide.\n\tSealedSecretClusterWideAnnotation = annoNs + \"cluster-wide\"\n\n\t// SealedSecretNamespaceWideAnnotation is the name for the annotation for\n\t// setting the secret to be available namespace wide.\n\tSealedSecretNamespaceWideAnnotation = annoNs + \"namespace-wide\"\n\n\t// SealedSecretManagedAnnotation is the name for the annotation for\n\t// flagging existing secrets to be managed by the Sealed Secrets controller.\n\tSealedSecretManagedAnnotation = annoNs + \"managed\"\n\n\t// SealedSecretPatchAnnotation is the name for the annotation for\n\t// flagging existing secrets to be patched instead of overwritten by the Sealed Secrets controller.\n\tSealedSecretPatchAnnotation = annoNs + \"patch\"\n\n\t// SealedSecretSkipSetOwnerReferencesAnnotation is the name for the annotation for\n\t// flagging the controller not to set owner reference to secret.\n\tSealedSecretSkipSetOwnerReferencesAnnotation = annoNs + \"skip-set-owner-references\"\n)\n\n// SecretTemplateSpec describes the structure a Secret should have\n// when created from a template.\ntype SecretTemplateSpec struct {\n\t// Standard object's metadata.\n\t// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata\n\t// +optional\n\t// +nullable\n\t// +kubebuilder:pruning:PreserveUnknownFields\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\" protobuf:\"bytes,1,opt,name=metadata\"`\n\n\t// Used to facilitate programmatic handling of secret data.\n\t// +optional\n\tType apiv1.SecretType `json:\"type,omitempty\" protobuf:\"bytes,3,opt,name=type,casttype=SecretType\"`\n\n\t// Immutable, if set to true, ensures that data stored in the Secret cannot\n\t// be updated (only object metadata can be modified).\n\t// If not set to true, the field can be modified at any time.\n\t// Defaulted to nil.\n\t// +optional\n\tImmutable *bool `json:\"immutable,omitempty\" protobuf:\"varint,5,opt,name=immutable\"`\n\n\t// Keys that should be templated using decrypted data.\n\t// +optional\n\t// +nullable\n\tData map[string]string `json:\"data,omitempty\"`\n}\n\n// SealedSecretSpec is the specification of a SealedSecret.\ntype SealedSecretSpec struct {\n\t// Template defines the structure of the Secret that will be\n\t// created from this sealed secret.\n\t// +optional\n\tTemplate SecretTemplateSpec `json:\"template,omitempty\"`\n\n\t// Data is deprecated and will be removed eventually. Use per-value EncryptedData instead.\n\tData          []byte                    `json:\"data,omitempty\"`\n\tEncryptedData SealedSecretEncryptedData `json:\"encryptedData\"`\n}\n\n// +kubebuilder:pruning:PreserveUnknownFields\ntype SealedSecretEncryptedData map[string]string\n\nfunc (s *SealedSecretEncryptedData) UnmarshalJSON(data []byte) error {\n\ttmp := map[string]string{}\n\t// drop error - likelihood of an error occurring is quite high due to the disabled schema validation, these errors.\n\t// would cause the controller to stop processing any SealedSecret.\n\t_ = json.Unmarshal(data, &tmp)\n\t*s = tmp\n\treturn nil\n}\n\n// SealedSecretConditionType describes the type of SealedSecret condition.\ntype SealedSecretConditionType string\n\nconst (\n\t// SealedSecretSynced means the SealedSecret has been decrypted and the Secret has been updated successfully.\n\tSealedSecretSynced SealedSecretConditionType = \"Synced\"\n)\n\n// SealedSecretCondition describes the state of a sealed secret at a certain point.\ntype SealedSecretCondition struct {\n\t// Type of condition for a sealed secret.\n\t// Valid value: \"Synced\"\n\tType SealedSecretConditionType `json:\"type\" protobuf:\"bytes,1,opt,name=type,casttype=DeploymentConditionType\"`\n\t// Status of the condition for a sealed secret.\n\t// Valid values for \"Synced\": \"True\", \"False\", or \"Unknown\".\n\tStatus apiv1.ConditionStatus `json:\"status\" protobuf:\"bytes,2,opt,name=status,casttype=k8s.io/api/core/v1.ConditionStatus\"`\n\t// The last time this condition was updated.\n\tLastUpdateTime metav1.Time `json:\"lastUpdateTime,omitempty\" protobuf:\"bytes,6,opt,name=lastUpdateTime\"`\n\t// Last time the condition transitioned from one status to another.\n\tLastTransitionTime metav1.Time `json:\"lastTransitionTime,omitempty\" protobuf:\"bytes,7,opt,name=lastTransitionTime\"`\n\t// The reason for the condition's last transition.\n\tReason string `json:\"reason,omitempty\" protobuf:\"bytes,4,opt,name=reason\"`\n\t// A human readable message indicating details about the transition.\n\tMessage string `json:\"message,omitempty\" protobuf:\"bytes,5,opt,name=message\"`\n}\n\n// SealedSecretStatus is the most recently observed status of the SealedSecret.\ntype SealedSecretStatus struct {\n\t// ObservedGeneration reflects the generation most recently observed by the sealed-secrets controller.\n\t// +optional\n\tObservedGeneration int64 `json:\"observedGeneration,omitempty\" protobuf:\"varint,3,opt,name=observedGeneration\"`\n\n\t// Represents the latest available observations of a sealed secret's current state.\n\t// +optional\n\t// +patchMergeKey=type\n\t// +patchStrategy=merge\n\tConditions []SealedSecretCondition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,6,rep,name=conditions\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n// +kubebuilder:subresource:status\n// +kubebuilder:printcolumn:name=\"Status\",type=\"string\",JSONPath=\".status.conditions[0].message\"\n// +kubebuilder:printcolumn:name=\"Synced\",type=\"string\",JSONPath=\".status.conditions[0].status\"\n// +kubebuilder:printcolumn:name=\"Age\",type=\"date\",JSONPath=\".metadata.creationTimestamp\"\n// +genclient\n\n// SealedSecret is the K8s representation of a \"sealed Secret\" - a\n// regular k8s Secret that has been sealed (encrypted) using the\n// controller's key.\ntype SealedSecret struct {\n\tmetav1.TypeMeta   `json:\",inline\"`\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec SealedSecretSpec `json:\"spec\"`\n\t// +optional\n\tStatus *SealedSecretStatus `json:\"status,omitempty\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// SealedSecretList represents a list of SealedSecrets.\ntype SealedSecretList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata\"`\n\n\tItems []SealedSecret `json:\"items\"`\n}\n\n// ByCreationTimestamp is used to sort a list of secrets.\ntype ByCreationTimestamp []apiv1.Secret\n\nfunc (s ByCreationTimestamp) Len() int {\n\treturn len(s)\n}\n\nfunc (s ByCreationTimestamp) Swap(i, j int) {\n\ts[i], s[j] = s[j], s[i]\n}\n\nfunc (s ByCreationTimestamp) Less(i, j int) bool {\n\treturn s[i].GetCreationTimestamp().Unix() < s[j].GetCreationTimestamp().Unix()\n}\n"
  },
  {
    "path": "pkg/apis/sealedsecrets/v1alpha1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n// Code generated by deepcopy-gen. DO NOT EDIT.\n\npackage v1alpha1\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 ByCreationTimestamp) DeepCopyInto(out *ByCreationTimestamp) {\n\t{\n\t\tin := &in\n\t\t*out = make(ByCreationTimestamp, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t\treturn\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ByCreationTimestamp.\nfunc (in ByCreationTimestamp) DeepCopy() ByCreationTimestamp {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ByCreationTimestamp)\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 *SealedSecret) DeepCopyInto(out *SealedSecret) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tif in.Status != nil {\n\t\tin, out := &in.Status, &out.Status\n\t\t*out = new(SealedSecretStatus)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SealedSecret.\nfunc (in *SealedSecret) DeepCopy() *SealedSecret {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(SealedSecret)\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 *SealedSecret) 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 *SealedSecretCondition) DeepCopyInto(out *SealedSecretCondition) {\n\t*out = *in\n\tin.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime)\n\tin.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SealedSecretCondition.\nfunc (in *SealedSecretCondition) DeepCopy() *SealedSecretCondition {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(SealedSecretCondition)\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 SealedSecretEncryptedData) DeepCopyInto(out *SealedSecretEncryptedData) {\n\t{\n\t\tin := &in\n\t\t*out = make(SealedSecretEncryptedData, len(*in))\n\t\tfor key, val := range *in {\n\t\t\t(*out)[key] = val\n\t\t}\n\t\treturn\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SealedSecretEncryptedData.\nfunc (in SealedSecretEncryptedData) DeepCopy() SealedSecretEncryptedData {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(SealedSecretEncryptedData)\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 *SealedSecretList) DeepCopyInto(out *SealedSecretList) {\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([]SealedSecret, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SealedSecretList.\nfunc (in *SealedSecretList) DeepCopy() *SealedSecretList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(SealedSecretList)\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 *SealedSecretList) 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 *SealedSecretSpec) DeepCopyInto(out *SealedSecretSpec) {\n\t*out = *in\n\tin.Template.DeepCopyInto(&out.Template)\n\tif in.Data != nil {\n\t\tin, out := &in.Data, &out.Data\n\t\t*out = make([]byte, len(*in))\n\t\tcopy(*out, *in)\n\t}\n\tif in.EncryptedData != nil {\n\t\tin, out := &in.EncryptedData, &out.EncryptedData\n\t\t*out = make(SealedSecretEncryptedData, len(*in))\n\t\tfor key, val := range *in {\n\t\t\t(*out)[key] = val\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SealedSecretSpec.\nfunc (in *SealedSecretSpec) DeepCopy() *SealedSecretSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(SealedSecretSpec)\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 *SealedSecretStatus) DeepCopyInto(out *SealedSecretStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]SealedSecretCondition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SealedSecretStatus.\nfunc (in *SealedSecretStatus) DeepCopy() *SealedSecretStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(SealedSecretStatus)\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 *SecretTemplateSpec) DeepCopyInto(out *SecretTemplateSpec) {\n\t*out = *in\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tif in.Immutable != nil {\n\t\tin, out := &in.Immutable, &out.Immutable\n\t\t*out = new(bool)\n\t\t**out = **in\n\t}\n\tif in.Data != nil {\n\t\tin, out := &in.Data, &out.Data\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\treturn\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretTemplateSpec.\nfunc (in *SecretTemplateSpec) DeepCopy() *SecretTemplateSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(SecretTemplateSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "pkg/buildinfo/version.go",
    "content": "package buildinfo\n\nimport \"runtime/debug\"\n\n// DefaultVersion is the default version string if it's unset.\nconst DefaultVersion = \"UNKNOWN\"\n\n// FallbackVersion initializes the automatic version detection.\nfunc FallbackVersion(v *string, unchanged string) {\n\tif *v != unchanged {\n\t\treturn\n\t}\n\tb, ok := debug.ReadBuildInfo()\n\tif !ok {\n\t\treturn\n\t}\n\tif modv := b.Main.Version; modv != \"(devel)\" {\n\t\t*v = modv\n\t}\n}\n"
  },
  {
    "path": "pkg/client/clientset/versioned/clientset.go",
    "content": "// Code generated by client-gen. DO NOT EDIT.\n\npackage versioned\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\tbitnamiv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned/typed/sealedsecrets/v1alpha1\"\n\tdiscovery \"k8s.io/client-go/discovery\"\n\trest \"k8s.io/client-go/rest\"\n\tflowcontrol \"k8s.io/client-go/util/flowcontrol\"\n)\n\ntype Interface interface {\n\tDiscovery() discovery.DiscoveryInterface\n\tBitnamiV1alpha1() bitnamiv1alpha1.BitnamiV1alpha1Interface\n}\n\n// Clientset contains the clients for groups.\ntype Clientset struct {\n\t*discovery.DiscoveryClient\n\tbitnamiV1alpha1 *bitnamiv1alpha1.BitnamiV1alpha1Client\n}\n\n// BitnamiV1alpha1 retrieves the BitnamiV1alpha1Client\nfunc (c *Clientset) BitnamiV1alpha1() bitnamiv1alpha1.BitnamiV1alpha1Interface {\n\treturn c.bitnamiV1alpha1\n}\n\n// Discovery retrieves the DiscoveryClient\nfunc (c *Clientset) Discovery() discovery.DiscoveryInterface {\n\tif c == nil {\n\t\treturn nil\n\t}\n\treturn c.DiscoveryClient\n}\n\n// NewForConfig creates a new Clientset for the given config.\n// If config's RateLimiter is not set and QPS and Burst are acceptable,\n// NewForConfig will generate a rate-limiter in configShallowCopy.\n// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),\n// where httpClient was generated with rest.HTTPClientFor(c).\nfunc NewForConfig(c *rest.Config) (*Clientset, error) {\n\tconfigShallowCopy := *c\n\n\tif configShallowCopy.UserAgent == \"\" {\n\t\tconfigShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent()\n\t}\n\n\t// share the transport between all clients\n\thttpClient, err := rest.HTTPClientFor(&configShallowCopy)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn NewForConfigAndClient(&configShallowCopy, httpClient)\n}\n\n// NewForConfigAndClient creates a new Clientset for the given config and http client.\n// Note the http client provided takes precedence over the configured transport values.\n// If config's RateLimiter is not set and QPS and Burst are acceptable,\n// NewForConfigAndClient will generate a rate-limiter in configShallowCopy.\nfunc NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) {\n\tconfigShallowCopy := *c\n\tif configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {\n\t\tif configShallowCopy.Burst <= 0 {\n\t\t\treturn nil, fmt.Errorf(\"burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0\")\n\t\t}\n\t\tconfigShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)\n\t}\n\n\tvar cs Clientset\n\tvar err error\n\tcs.bitnamiV1alpha1, err = bitnamiv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &cs, nil\n}\n\n// NewForConfigOrDie creates a new Clientset for the given config and\n// panics if there is an error in the config.\nfunc NewForConfigOrDie(c *rest.Config) *Clientset {\n\tcs, err := NewForConfig(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn cs\n}\n\n// New creates a new Clientset for the given RESTClient.\nfunc New(c rest.Interface) *Clientset {\n\tvar cs Clientset\n\tcs.bitnamiV1alpha1 = bitnamiv1alpha1.New(c)\n\n\tcs.DiscoveryClient = discovery.NewDiscoveryClient(c)\n\treturn &cs\n}\n"
  },
  {
    "path": "pkg/client/clientset/versioned/fake/clientset_generated.go",
    "content": "// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tclientset \"github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned\"\n\tbitnamiv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned/typed/sealedsecrets/v1alpha1\"\n\tfakebitnamiv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned/typed/sealedsecrets/v1alpha1/fake\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/watch\"\n\t\"k8s.io/client-go/discovery\"\n\tfakediscovery \"k8s.io/client-go/discovery/fake\"\n\t\"k8s.io/client-go/testing\"\n)\n\n// NewSimpleClientset returns a clientset that will respond with the provided objects.\n// It's backed by a very simple object tracker that processes creates, updates and deletions as-is,\n// without applying any validations and/or defaults. It shouldn't be considered a replacement\n// for a real clientset and is mostly useful in simple unit tests.\nfunc NewSimpleClientset(objects ...runtime.Object) *Clientset {\n\to := testing.NewObjectTracker(scheme, codecs.UniversalDecoder())\n\tfor _, obj := range objects {\n\t\tif err := o.Add(obj); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\n\tcs := &Clientset{tracker: o}\n\tcs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake}\n\tcs.AddReactor(\"*\", \"*\", testing.ObjectReaction(o))\n\tcs.AddWatchReactor(\"*\", func(action testing.Action) (handled bool, ret watch.Interface, err error) {\n\t\tgvr := action.GetResource()\n\t\tns := action.GetNamespace()\n\t\twatch, err := o.Watch(gvr, ns)\n\t\tif err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\t\treturn true, watch, nil\n\t})\n\n\treturn cs\n}\n\n// Clientset implements clientset.Interface. Meant to be embedded into a\n// struct to get a default implementation. This makes faking out just the method\n// you want to test easier.\ntype Clientset struct {\n\ttesting.Fake\n\tdiscovery *fakediscovery.FakeDiscovery\n\ttracker   testing.ObjectTracker\n}\n\nfunc (c *Clientset) Discovery() discovery.DiscoveryInterface {\n\treturn c.discovery\n}\n\nfunc (c *Clientset) Tracker() testing.ObjectTracker {\n\treturn c.tracker\n}\n\nvar (\n\t_ clientset.Interface = &Clientset{}\n\t_ testing.FakeClient  = &Clientset{}\n)\n\n// BitnamiV1alpha1 retrieves the BitnamiV1alpha1Client\nfunc (c *Clientset) BitnamiV1alpha1() bitnamiv1alpha1.BitnamiV1alpha1Interface {\n\treturn &fakebitnamiv1alpha1.FakeBitnamiV1alpha1{Fake: &c.Fake}\n}\n"
  },
  {
    "path": "pkg/client/clientset/versioned/fake/doc.go",
    "content": "// Code generated by client-gen. DO NOT EDIT.\n\n// This package has the automatically generated fake clientset.\npackage fake\n"
  },
  {
    "path": "pkg/client/clientset/versioned/fake/register.go",
    "content": "// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tbitnamiv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/apis/sealedsecrets/v1alpha1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\tschema \"k8s.io/apimachinery/pkg/runtime/schema\"\n\tserializer \"k8s.io/apimachinery/pkg/runtime/serializer\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n)\n\nvar scheme = runtime.NewScheme()\nvar codecs = serializer.NewCodecFactory(scheme)\n\nvar localSchemeBuilder = runtime.SchemeBuilder{\n\tbitnamiv1alpha1.AddToScheme,\n}\n\n// AddToScheme adds all types of this clientset into the given scheme. This allows composition\n// of clientsets, like in:\n//\n//\timport (\n//\t  \"k8s.io/client-go/kubernetes\"\n//\t  clientsetscheme \"k8s.io/client-go/kubernetes/scheme\"\n//\t  aggregatorclientsetscheme \"k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme\"\n//\t)\n//\n//\tkclientset, _ := kubernetes.NewForConfig(c)\n//\t_ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)\n//\n// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types\n// correctly.\nvar AddToScheme = localSchemeBuilder.AddToScheme\n\nfunc init() {\n\tv1.AddToGroupVersion(scheme, schema.GroupVersion{Version: \"v1\"})\n\tutilruntime.Must(AddToScheme(scheme))\n}\n"
  },
  {
    "path": "pkg/client/clientset/versioned/scheme/doc.go",
    "content": "// Code generated by client-gen. DO NOT EDIT.\n\n// This package contains the scheme of the automatically generated clientset.\npackage scheme\n"
  },
  {
    "path": "pkg/client/clientset/versioned/scheme/register.go",
    "content": "// Code generated by client-gen. DO NOT EDIT.\n\npackage scheme\n\nimport (\n\tbitnamiv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/apis/sealedsecrets/v1alpha1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\tschema \"k8s.io/apimachinery/pkg/runtime/schema\"\n\tserializer \"k8s.io/apimachinery/pkg/runtime/serializer\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n)\n\nvar Scheme = runtime.NewScheme()\nvar Codecs = serializer.NewCodecFactory(Scheme)\nvar ParameterCodec = runtime.NewParameterCodec(Scheme)\nvar localSchemeBuilder = runtime.SchemeBuilder{\n\tbitnamiv1alpha1.AddToScheme,\n}\n\n// AddToScheme adds all types of this clientset into the given scheme. This allows composition\n// of clientsets, like in:\n//\n//\timport (\n//\t  \"k8s.io/client-go/kubernetes\"\n//\t  clientsetscheme \"k8s.io/client-go/kubernetes/scheme\"\n//\t  aggregatorclientsetscheme \"k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme\"\n//\t)\n//\n//\tkclientset, _ := kubernetes.NewForConfig(c)\n//\t_ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)\n//\n// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types\n// correctly.\nvar AddToScheme = localSchemeBuilder.AddToScheme\n\nfunc init() {\n\tv1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: \"v1\"})\n\tutilruntime.Must(AddToScheme(Scheme))\n}\n"
  },
  {
    "path": "pkg/client/clientset/versioned/typed/sealedsecrets/v1alpha1/doc.go",
    "content": "// Code generated by client-gen. DO NOT EDIT.\n\n// This package has the automatically generated typed clients.\npackage v1alpha1\n"
  },
  {
    "path": "pkg/client/clientset/versioned/typed/sealedsecrets/v1alpha1/fake/doc.go",
    "content": "// Code generated by client-gen. DO NOT EDIT.\n\n// Package fake has the automatically generated clients.\npackage fake\n"
  },
  {
    "path": "pkg/client/clientset/versioned/typed/sealedsecrets/v1alpha1/fake/fake_sealedsecret.go",
    "content": "// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\t\"context\"\n\n\tv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/apis/sealedsecrets/v1alpha1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\tlabels \"k8s.io/apimachinery/pkg/labels\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\ttesting \"k8s.io/client-go/testing\"\n)\n\n// FakeSealedSecrets implements SealedSecretInterface\ntype FakeSealedSecrets struct {\n\tFake *FakeBitnamiV1alpha1\n\tns   string\n}\n\nvar sealedsecretsResource = v1alpha1.SchemeGroupVersion.WithResource(\"sealedsecrets\")\n\nvar sealedsecretsKind = v1alpha1.SchemeGroupVersion.WithKind(\"SealedSecret\")\n\n// Get takes name of the sealedSecret, and returns the corresponding sealedSecret object, and an error if there is any.\nfunc (c *FakeSealedSecrets) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.SealedSecret, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewGetAction(sealedsecretsResource, c.ns, name), &v1alpha1.SealedSecret{})\n\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.SealedSecret), err\n}\n\n// List takes label and field selectors, and returns the list of SealedSecrets that match those selectors.\nfunc (c *FakeSealedSecrets) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.SealedSecretList, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewListAction(sealedsecretsResource, sealedsecretsKind, c.ns, opts), &v1alpha1.SealedSecretList{})\n\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\n\tlabel, _, _ := testing.ExtractFromListOptions(opts)\n\tif label == nil {\n\t\tlabel = labels.Everything()\n\t}\n\tlist := &v1alpha1.SealedSecretList{ListMeta: obj.(*v1alpha1.SealedSecretList).ListMeta}\n\tfor _, item := range obj.(*v1alpha1.SealedSecretList).Items {\n\t\tif label.Matches(labels.Set(item.Labels)) {\n\t\t\tlist.Items = append(list.Items, item)\n\t\t}\n\t}\n\treturn list, err\n}\n\n// Watch returns a watch.Interface that watches the requested sealedSecrets.\nfunc (c *FakeSealedSecrets) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {\n\treturn c.Fake.\n\t\tInvokesWatch(testing.NewWatchAction(sealedsecretsResource, c.ns, opts))\n\n}\n\n// Create takes the representation of a sealedSecret and creates it.  Returns the server's representation of the sealedSecret, and an error, if there is any.\nfunc (c *FakeSealedSecrets) Create(ctx context.Context, sealedSecret *v1alpha1.SealedSecret, opts v1.CreateOptions) (result *v1alpha1.SealedSecret, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewCreateAction(sealedsecretsResource, c.ns, sealedSecret), &v1alpha1.SealedSecret{})\n\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.SealedSecret), err\n}\n\n// Update takes the representation of a sealedSecret and updates it. Returns the server's representation of the sealedSecret, and an error, if there is any.\nfunc (c *FakeSealedSecrets) Update(ctx context.Context, sealedSecret *v1alpha1.SealedSecret, opts v1.UpdateOptions) (result *v1alpha1.SealedSecret, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewUpdateAction(sealedsecretsResource, c.ns, sealedSecret), &v1alpha1.SealedSecret{})\n\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.SealedSecret), err\n}\n\n// UpdateStatus was generated because the type contains a Status member.\n// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().\nfunc (c *FakeSealedSecrets) UpdateStatus(ctx context.Context, sealedSecret *v1alpha1.SealedSecret, opts v1.UpdateOptions) (*v1alpha1.SealedSecret, error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewUpdateSubresourceAction(sealedsecretsResource, \"status\", c.ns, sealedSecret), &v1alpha1.SealedSecret{})\n\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.SealedSecret), err\n}\n\n// Delete takes name of the sealedSecret and deletes it. Returns an error if one occurs.\nfunc (c *FakeSealedSecrets) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {\n\t_, err := c.Fake.\n\t\tInvokes(testing.NewDeleteActionWithOptions(sealedsecretsResource, c.ns, name, opts), &v1alpha1.SealedSecret{})\n\n\treturn err\n}\n\n// DeleteCollection deletes a collection of objects.\nfunc (c *FakeSealedSecrets) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {\n\taction := testing.NewDeleteCollectionAction(sealedsecretsResource, c.ns, listOpts)\n\n\t_, err := c.Fake.Invokes(action, &v1alpha1.SealedSecretList{})\n\treturn err\n}\n\n// Patch applies the patch and returns the patched sealedSecret.\nfunc (c *FakeSealedSecrets) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.SealedSecret, err error) {\n\tobj, err := c.Fake.\n\t\tInvokes(testing.NewPatchSubresourceAction(sealedsecretsResource, c.ns, name, pt, data, subresources...), &v1alpha1.SealedSecret{})\n\n\tif obj == nil {\n\t\treturn nil, err\n\t}\n\treturn obj.(*v1alpha1.SealedSecret), err\n}\n"
  },
  {
    "path": "pkg/client/clientset/versioned/typed/sealedsecrets/v1alpha1/fake/fake_sealedsecrets_client.go",
    "content": "// Code generated by client-gen. DO NOT EDIT.\n\npackage fake\n\nimport (\n\tv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned/typed/sealedsecrets/v1alpha1\"\n\trest \"k8s.io/client-go/rest\"\n\ttesting \"k8s.io/client-go/testing\"\n)\n\ntype FakeBitnamiV1alpha1 struct {\n\t*testing.Fake\n}\n\nfunc (c *FakeBitnamiV1alpha1) SealedSecrets(namespace string) v1alpha1.SealedSecretInterface {\n\treturn &FakeSealedSecrets{c, namespace}\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *FakeBitnamiV1alpha1) RESTClient() rest.Interface {\n\tvar ret *rest.RESTClient\n\treturn ret\n}\n"
  },
  {
    "path": "pkg/client/clientset/versioned/typed/sealedsecrets/v1alpha1/generated_expansion.go",
    "content": "// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha1\n\ntype SealedSecretExpansion interface{}\n"
  },
  {
    "path": "pkg/client/clientset/versioned/typed/sealedsecrets/v1alpha1/sealedsecret.go",
    "content": "// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\tv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/apis/sealedsecrets/v1alpha1\"\n\tscheme \"github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned/scheme\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\ttypes \"k8s.io/apimachinery/pkg/types\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\trest \"k8s.io/client-go/rest\"\n)\n\n// SealedSecretsGetter has a method to return a SealedSecretInterface.\n// A group's client should implement this interface.\ntype SealedSecretsGetter interface {\n\tSealedSecrets(namespace string) SealedSecretInterface\n}\n\n// SealedSecretInterface has methods to work with SealedSecret resources.\ntype SealedSecretInterface interface {\n\tCreate(ctx context.Context, sealedSecret *v1alpha1.SealedSecret, opts v1.CreateOptions) (*v1alpha1.SealedSecret, error)\n\tUpdate(ctx context.Context, sealedSecret *v1alpha1.SealedSecret, opts v1.UpdateOptions) (*v1alpha1.SealedSecret, error)\n\tUpdateStatus(ctx context.Context, sealedSecret *v1alpha1.SealedSecret, opts v1.UpdateOptions) (*v1alpha1.SealedSecret, error)\n\tDelete(ctx context.Context, name string, opts v1.DeleteOptions) error\n\tDeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error\n\tGet(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.SealedSecret, error)\n\tList(ctx context.Context, opts v1.ListOptions) (*v1alpha1.SealedSecretList, error)\n\tWatch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)\n\tPatch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.SealedSecret, err error)\n\tSealedSecretExpansion\n}\n\n// sealedSecrets implements SealedSecretInterface\ntype sealedSecrets struct {\n\tclient rest.Interface\n\tns     string\n}\n\n// newSealedSecrets returns a SealedSecrets\nfunc newSealedSecrets(c *BitnamiV1alpha1Client, namespace string) *sealedSecrets {\n\treturn &sealedSecrets{\n\t\tclient: c.RESTClient(),\n\t\tns:     namespace,\n\t}\n}\n\n// Get takes name of the sealedSecret, and returns the corresponding sealedSecret object, and an error if there is any.\nfunc (c *sealedSecrets) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.SealedSecret, err error) {\n\tresult = &v1alpha1.SealedSecret{}\n\terr = c.client.Get().\n\t\tNamespace(c.ns).\n\t\tResource(\"sealedsecrets\").\n\t\tName(name).\n\t\tVersionedParams(&options, scheme.ParameterCodec).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// List takes label and field selectors, and returns the list of SealedSecrets that match those selectors.\nfunc (c *sealedSecrets) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.SealedSecretList, err error) {\n\tvar timeout time.Duration\n\tif opts.TimeoutSeconds != nil {\n\t\ttimeout = time.Duration(*opts.TimeoutSeconds) * time.Second\n\t}\n\tresult = &v1alpha1.SealedSecretList{}\n\terr = c.client.Get().\n\t\tNamespace(c.ns).\n\t\tResource(\"sealedsecrets\").\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tTimeout(timeout).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// Watch returns a watch.Interface that watches the requested sealedSecrets.\nfunc (c *sealedSecrets) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {\n\tvar timeout time.Duration\n\tif opts.TimeoutSeconds != nil {\n\t\ttimeout = time.Duration(*opts.TimeoutSeconds) * time.Second\n\t}\n\topts.Watch = true\n\treturn c.client.Get().\n\t\tNamespace(c.ns).\n\t\tResource(\"sealedsecrets\").\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tTimeout(timeout).\n\t\tWatch(ctx)\n}\n\n// Create takes the representation of a sealedSecret and creates it.  Returns the server's representation of the sealedSecret, and an error, if there is any.\nfunc (c *sealedSecrets) Create(ctx context.Context, sealedSecret *v1alpha1.SealedSecret, opts v1.CreateOptions) (result *v1alpha1.SealedSecret, err error) {\n\tresult = &v1alpha1.SealedSecret{}\n\terr = c.client.Post().\n\t\tNamespace(c.ns).\n\t\tResource(\"sealedsecrets\").\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tBody(sealedSecret).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// Update takes the representation of a sealedSecret and updates it. Returns the server's representation of the sealedSecret, and an error, if there is any.\nfunc (c *sealedSecrets) Update(ctx context.Context, sealedSecret *v1alpha1.SealedSecret, opts v1.UpdateOptions) (result *v1alpha1.SealedSecret, err error) {\n\tresult = &v1alpha1.SealedSecret{}\n\terr = c.client.Put().\n\t\tNamespace(c.ns).\n\t\tResource(\"sealedsecrets\").\n\t\tName(sealedSecret.Name).\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tBody(sealedSecret).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// UpdateStatus was generated because the type contains a Status member.\n// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().\nfunc (c *sealedSecrets) UpdateStatus(ctx context.Context, sealedSecret *v1alpha1.SealedSecret, opts v1.UpdateOptions) (result *v1alpha1.SealedSecret, err error) {\n\tresult = &v1alpha1.SealedSecret{}\n\terr = c.client.Put().\n\t\tNamespace(c.ns).\n\t\tResource(\"sealedsecrets\").\n\t\tName(sealedSecret.Name).\n\t\tSubResource(\"status\").\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tBody(sealedSecret).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n\n// Delete takes name of the sealedSecret and deletes it. Returns an error if one occurs.\nfunc (c *sealedSecrets) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {\n\treturn c.client.Delete().\n\t\tNamespace(c.ns).\n\t\tResource(\"sealedsecrets\").\n\t\tName(name).\n\t\tBody(&opts).\n\t\tDo(ctx).\n\t\tError()\n}\n\n// DeleteCollection deletes a collection of objects.\nfunc (c *sealedSecrets) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {\n\tvar timeout time.Duration\n\tif listOpts.TimeoutSeconds != nil {\n\t\ttimeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second\n\t}\n\treturn c.client.Delete().\n\t\tNamespace(c.ns).\n\t\tResource(\"sealedsecrets\").\n\t\tVersionedParams(&listOpts, scheme.ParameterCodec).\n\t\tTimeout(timeout).\n\t\tBody(&opts).\n\t\tDo(ctx).\n\t\tError()\n}\n\n// Patch applies the patch and returns the patched sealedSecret.\nfunc (c *sealedSecrets) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.SealedSecret, err error) {\n\tresult = &v1alpha1.SealedSecret{}\n\terr = c.client.Patch(pt).\n\t\tNamespace(c.ns).\n\t\tResource(\"sealedsecrets\").\n\t\tName(name).\n\t\tSubResource(subresources...).\n\t\tVersionedParams(&opts, scheme.ParameterCodec).\n\t\tBody(data).\n\t\tDo(ctx).\n\t\tInto(result)\n\treturn\n}\n"
  },
  {
    "path": "pkg/client/clientset/versioned/typed/sealedsecrets/v1alpha1/sealedsecrets_client.go",
    "content": "// Code generated by client-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\t\"net/http\"\n\n\tv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/apis/sealedsecrets/v1alpha1\"\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned/scheme\"\n\trest \"k8s.io/client-go/rest\"\n)\n\ntype BitnamiV1alpha1Interface interface {\n\tRESTClient() rest.Interface\n\tSealedSecretsGetter\n}\n\n// BitnamiV1alpha1Client is used to interact with features provided by the bitnami.com group.\ntype BitnamiV1alpha1Client struct {\n\trestClient rest.Interface\n}\n\nfunc (c *BitnamiV1alpha1Client) SealedSecrets(namespace string) SealedSecretInterface {\n\treturn newSealedSecrets(c, namespace)\n}\n\n// NewForConfig creates a new BitnamiV1alpha1Client for the given config.\n// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),\n// where httpClient was generated with rest.HTTPClientFor(c).\nfunc NewForConfig(c *rest.Config) (*BitnamiV1alpha1Client, error) {\n\tconfig := *c\n\tif err := setConfigDefaults(&config); err != nil {\n\t\treturn nil, err\n\t}\n\thttpClient, err := rest.HTTPClientFor(&config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn NewForConfigAndClient(&config, httpClient)\n}\n\n// NewForConfigAndClient creates a new BitnamiV1alpha1Client for the given config and http client.\n// Note the http client provided takes precedence over the configured transport values.\nfunc NewForConfigAndClient(c *rest.Config, h *http.Client) (*BitnamiV1alpha1Client, error) {\n\tconfig := *c\n\tif err := setConfigDefaults(&config); err != nil {\n\t\treturn nil, err\n\t}\n\tclient, err := rest.RESTClientForConfigAndClient(&config, h)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &BitnamiV1alpha1Client{client}, nil\n}\n\n// NewForConfigOrDie creates a new BitnamiV1alpha1Client for the given config and\n// panics if there is an error in the config.\nfunc NewForConfigOrDie(c *rest.Config) *BitnamiV1alpha1Client {\n\tclient, err := NewForConfig(c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn client\n}\n\n// New creates a new BitnamiV1alpha1Client for the given RESTClient.\nfunc New(c rest.Interface) *BitnamiV1alpha1Client {\n\treturn &BitnamiV1alpha1Client{c}\n}\n\nfunc setConfigDefaults(config *rest.Config) error {\n\tgv := v1alpha1.SchemeGroupVersion\n\tconfig.GroupVersion = &gv\n\tconfig.APIPath = \"/apis\"\n\tconfig.NegotiatedSerializer = scheme.Codecs.WithoutConversion()\n\n\tif config.UserAgent == \"\" {\n\t\tconfig.UserAgent = rest.DefaultKubernetesUserAgent()\n\t}\n\n\treturn nil\n}\n\n// RESTClient returns a RESTClient that is used to communicate\n// with API server by this client implementation.\nfunc (c *BitnamiV1alpha1Client) RESTClient() rest.Interface {\n\tif c == nil {\n\t\treturn nil\n\t}\n\treturn c.restClient\n}\n"
  },
  {
    "path": "pkg/client/informers/externalversions/factory.go",
    "content": "// Code generated by informer-gen. DO NOT EDIT.\n\npackage externalversions\n\nimport (\n\treflect \"reflect\"\n\tsync \"sync\"\n\ttime \"time\"\n\n\tversioned \"github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned\"\n\tinternalinterfaces \"github.com/bitnami-labs/sealed-secrets/pkg/client/informers/externalversions/internalinterfaces\"\n\tsealedsecrets \"github.com/bitnami-labs/sealed-secrets/pkg/client/informers/externalversions/sealedsecrets\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\tschema \"k8s.io/apimachinery/pkg/runtime/schema\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// SharedInformerOption defines the functional option type for SharedInformerFactory.\ntype SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory\n\ntype sharedInformerFactory struct {\n\tclient           versioned.Interface\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tlock             sync.Mutex\n\tdefaultResync    time.Duration\n\tcustomResync     map[reflect.Type]time.Duration\n\ttransform        cache.TransformFunc\n\n\tinformers map[reflect.Type]cache.SharedIndexInformer\n\t// startedInformers is used for tracking which informers have been started.\n\t// This allows Start() to be called multiple times safely.\n\tstartedInformers map[reflect.Type]bool\n\t// wg tracks how many goroutines were started.\n\twg sync.WaitGroup\n\t// shuttingDown is true when Shutdown has been called. It may still be running\n\t// because it needs to wait for goroutines.\n\tshuttingDown bool\n}\n\n// WithCustomResyncConfig sets a custom resync period for the specified informer types.\nfunc WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) SharedInformerOption {\n\treturn func(factory *sharedInformerFactory) *sharedInformerFactory {\n\t\tfor k, v := range resyncConfig {\n\t\t\tfactory.customResync[reflect.TypeOf(k)] = v\n\t\t}\n\t\treturn factory\n\t}\n}\n\n// WithTweakListOptions sets a custom filter on all listers of the configured SharedInformerFactory.\nfunc WithTweakListOptions(tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerOption {\n\treturn func(factory *sharedInformerFactory) *sharedInformerFactory {\n\t\tfactory.tweakListOptions = tweakListOptions\n\t\treturn factory\n\t}\n}\n\n// WithNamespace limits the SharedInformerFactory to the specified namespace.\nfunc WithNamespace(namespace string) SharedInformerOption {\n\treturn func(factory *sharedInformerFactory) *sharedInformerFactory {\n\t\tfactory.namespace = namespace\n\t\treturn factory\n\t}\n}\n\n// WithTransform sets a transform on all informers.\nfunc WithTransform(transform cache.TransformFunc) SharedInformerOption {\n\treturn func(factory *sharedInformerFactory) *sharedInformerFactory {\n\t\tfactory.transform = transform\n\t\treturn factory\n\t}\n}\n\n// NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces.\nfunc NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory {\n\treturn NewSharedInformerFactoryWithOptions(client, defaultResync)\n}\n\n// NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory.\n// Listers obtained via this SharedInformerFactory will be subject to the same filters\n// as specified here.\n// Deprecated: Please use NewSharedInformerFactoryWithOptions instead\nfunc NewFilteredSharedInformerFactory(client versioned.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory {\n\treturn NewSharedInformerFactoryWithOptions(client, defaultResync, WithNamespace(namespace), WithTweakListOptions(tweakListOptions))\n}\n\n// NewSharedInformerFactoryWithOptions constructs a new instance of a SharedInformerFactory with additional options.\nfunc NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResync time.Duration, options ...SharedInformerOption) SharedInformerFactory {\n\tfactory := &sharedInformerFactory{\n\t\tclient:           client,\n\t\tnamespace:        v1.NamespaceAll,\n\t\tdefaultResync:    defaultResync,\n\t\tinformers:        make(map[reflect.Type]cache.SharedIndexInformer),\n\t\tstartedInformers: make(map[reflect.Type]bool),\n\t\tcustomResync:     make(map[reflect.Type]time.Duration),\n\t}\n\n\t// Apply all options\n\tfor _, opt := range options {\n\t\tfactory = opt(factory)\n\t}\n\n\treturn factory\n}\n\nfunc (f *sharedInformerFactory) Start(stopCh <-chan struct{}) {\n\tf.lock.Lock()\n\tdefer f.lock.Unlock()\n\n\tif f.shuttingDown {\n\t\treturn\n\t}\n\n\tfor informerType, informer := range f.informers {\n\t\tif !f.startedInformers[informerType] {\n\t\t\tf.wg.Add(1)\n\t\t\t// We need a new variable in each loop iteration,\n\t\t\t// otherwise the goroutine would use the loop variable\n\t\t\t// and that keeps changing.\n\t\t\tinformer := informer\n\t\t\tgo func() {\n\t\t\t\tdefer f.wg.Done()\n\t\t\t\tinformer.Run(stopCh)\n\t\t\t}()\n\t\t\tf.startedInformers[informerType] = true\n\t\t}\n\t}\n}\n\nfunc (f *sharedInformerFactory) Shutdown() {\n\tf.lock.Lock()\n\tf.shuttingDown = true\n\tf.lock.Unlock()\n\n\t// Will return immediately if there is nothing to wait for.\n\tf.wg.Wait()\n}\n\nfunc (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool {\n\tinformers := func() map[reflect.Type]cache.SharedIndexInformer {\n\t\tf.lock.Lock()\n\t\tdefer f.lock.Unlock()\n\n\t\tinformers := map[reflect.Type]cache.SharedIndexInformer{}\n\t\tfor informerType, informer := range f.informers {\n\t\t\tif f.startedInformers[informerType] {\n\t\t\t\tinformers[informerType] = informer\n\t\t\t}\n\t\t}\n\t\treturn informers\n\t}()\n\n\tres := map[reflect.Type]bool{}\n\tfor informType, informer := range informers {\n\t\tres[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced)\n\t}\n\treturn res\n}\n\n// InformerFor returns the SharedIndexInformer for obj using an internal\n// client.\nfunc (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer {\n\tf.lock.Lock()\n\tdefer f.lock.Unlock()\n\n\tinformerType := reflect.TypeOf(obj)\n\tinformer, exists := f.informers[informerType]\n\tif exists {\n\t\treturn informer\n\t}\n\n\tresyncPeriod, exists := f.customResync[informerType]\n\tif !exists {\n\t\tresyncPeriod = f.defaultResync\n\t}\n\n\tinformer = newFunc(f.client, resyncPeriod)\n\tinformer.SetTransform(f.transform)\n\tf.informers[informerType] = informer\n\n\treturn informer\n}\n\n// SharedInformerFactory provides shared informers for resources in all known\n// API group versions.\n//\n// It is typically used like this:\n//\n//\tctx, cancel := context.Background()\n//\tdefer cancel()\n//\tfactory := NewSharedInformerFactory(client, resyncPeriod)\n//\tdefer factory.WaitForStop()    // Returns immediately if nothing was started.\n//\tgenericInformer := factory.ForResource(resource)\n//\ttypedInformer := factory.SomeAPIGroup().V1().SomeType()\n//\tfactory.Start(ctx.Done())          // Start processing these informers.\n//\tsynced := factory.WaitForCacheSync(ctx.Done())\n//\tfor v, ok := range synced {\n//\t    if !ok {\n//\t        fmt.Fprintf(os.Stderr, \"caches failed to sync: %v\", v)\n//\t        return\n//\t    }\n//\t}\n//\n//\t// Creating informers can also be created after Start, but then\n//\t// Start must be called again:\n//\tanotherGenericInformer := factory.ForResource(resource)\n//\tfactory.Start(ctx.Done())\ntype SharedInformerFactory interface {\n\tinternalinterfaces.SharedInformerFactory\n\n\t// Start initializes all requested informers. They are handled in goroutines\n\t// which run until the stop channel gets closed.\n\tStart(stopCh <-chan struct{})\n\n\t// Shutdown marks a factory as shutting down. At that point no new\n\t// informers can be started anymore and Start will return without\n\t// doing anything.\n\t//\n\t// In addition, Shutdown blocks until all goroutines have terminated. For that\n\t// to happen, the close channel(s) that they were started with must be closed,\n\t// either before Shutdown gets called or while it is waiting.\n\t//\n\t// Shutdown may be called multiple times, even concurrently. All such calls will\n\t// block until all goroutines have terminated.\n\tShutdown()\n\n\t// WaitForCacheSync blocks until all started informers' caches were synced\n\t// or the stop channel gets closed.\n\tWaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool\n\n\t// ForResource gives generic access to a shared informer of the matching type.\n\tForResource(resource schema.GroupVersionResource) (GenericInformer, error)\n\n\t// InformerFor returns the SharedIndexInformer for obj using an internal\n\t// client.\n\tInformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer\n\n\tBitnami() sealedsecrets.Interface\n}\n\nfunc (f *sharedInformerFactory) Bitnami() sealedsecrets.Interface {\n\treturn sealedsecrets.New(f, f.namespace, f.tweakListOptions)\n}\n"
  },
  {
    "path": "pkg/client/informers/externalversions/generic.go",
    "content": "// Code generated by informer-gen. DO NOT EDIT.\n\npackage externalversions\n\nimport (\n\t\"fmt\"\n\n\tv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/apis/sealedsecrets/v1alpha1\"\n\tschema \"k8s.io/apimachinery/pkg/runtime/schema\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// GenericInformer is type of SharedIndexInformer which will locate and delegate to other\n// sharedInformers based on type\ntype GenericInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() cache.GenericLister\n}\n\ntype genericInformer struct {\n\tinformer cache.SharedIndexInformer\n\tresource schema.GroupResource\n}\n\n// Informer returns the SharedIndexInformer.\nfunc (f *genericInformer) Informer() cache.SharedIndexInformer {\n\treturn f.informer\n}\n\n// Lister returns the GenericLister.\nfunc (f *genericInformer) Lister() cache.GenericLister {\n\treturn cache.NewGenericLister(f.Informer().GetIndexer(), f.resource)\n}\n\n// ForResource gives generic access to a shared informer of the matching type\n// TODO extend this to unknown resources with a client pool\nfunc (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) {\n\tswitch resource {\n\t// Group=bitnami.com, Version=v1alpha1\n\tcase v1alpha1.SchemeGroupVersion.WithResource(\"sealedsecrets\"):\n\t\treturn &genericInformer{resource: resource.GroupResource(), informer: f.Bitnami().V1alpha1().SealedSecrets().Informer()}, nil\n\n\t}\n\n\treturn nil, fmt.Errorf(\"no informer found for %v\", resource)\n}\n"
  },
  {
    "path": "pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go",
    "content": "// Code generated by informer-gen. DO NOT EDIT.\n\npackage internalinterfaces\n\nimport (\n\ttime \"time\"\n\n\tversioned \"github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer.\ntype NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer\n\n// SharedInformerFactory a small interface to allow for adding an informer without an import cycle\ntype SharedInformerFactory interface {\n\tStart(stopCh <-chan struct{})\n\tInformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer\n}\n\n// TweakListOptionsFunc is a function that transforms a v1.ListOptions.\ntype TweakListOptionsFunc func(*v1.ListOptions)\n"
  },
  {
    "path": "pkg/client/informers/externalversions/sealedsecrets/interface.go",
    "content": "// Code generated by informer-gen. DO NOT EDIT.\n\npackage sealedsecrets\n\nimport (\n\tinternalinterfaces \"github.com/bitnami-labs/sealed-secrets/pkg/client/informers/externalversions/internalinterfaces\"\n\tv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/client/informers/externalversions/sealedsecrets/v1alpha1\"\n)\n\n// Interface provides access to each of this group's versions.\ntype Interface interface {\n\t// V1alpha1 provides access to shared informers for resources in V1alpha1.\n\tV1alpha1() v1alpha1.Interface\n}\n\ntype group struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// V1alpha1 returns a new v1alpha1.Interface.\nfunc (g *group) V1alpha1() v1alpha1.Interface {\n\treturn v1alpha1.New(g.factory, g.namespace, g.tweakListOptions)\n}\n"
  },
  {
    "path": "pkg/client/informers/externalversions/sealedsecrets/v1alpha1/interface.go",
    "content": "// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tinternalinterfaces \"github.com/bitnami-labs/sealed-secrets/pkg/client/informers/externalversions/internalinterfaces\"\n)\n\n// Interface provides access to all the informers in this group version.\ntype Interface interface {\n\t// SealedSecrets returns a SealedSecretInformer.\n\tSealedSecrets() SealedSecretInformer\n}\n\ntype version struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\tnamespace        string\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n}\n\n// New returns a new Interface.\nfunc New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {\n\treturn &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}\n}\n\n// SealedSecrets returns a SealedSecretInformer.\nfunc (v *version) SealedSecrets() SealedSecretInformer {\n\treturn &sealedSecretInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}\n}\n"
  },
  {
    "path": "pkg/client/informers/externalversions/sealedsecrets/v1alpha1/sealedsecret.go",
    "content": "// Code generated by informer-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\t\"context\"\n\ttime \"time\"\n\n\tsealedsecretsv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/apis/sealedsecrets/v1alpha1\"\n\tversioned \"github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned\"\n\tinternalinterfaces \"github.com/bitnami-labs/sealed-secrets/pkg/client/informers/externalversions/internalinterfaces\"\n\tv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/client/listers/sealedsecrets/v1alpha1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\truntime \"k8s.io/apimachinery/pkg/runtime\"\n\twatch \"k8s.io/apimachinery/pkg/watch\"\n\tcache \"k8s.io/client-go/tools/cache\"\n)\n\n// SealedSecretInformer provides access to a shared informer and lister for\n// SealedSecrets.\ntype SealedSecretInformer interface {\n\tInformer() cache.SharedIndexInformer\n\tLister() v1alpha1.SealedSecretLister\n}\n\ntype sealedSecretInformer struct {\n\tfactory          internalinterfaces.SharedInformerFactory\n\ttweakListOptions internalinterfaces.TweakListOptionsFunc\n\tnamespace        string\n}\n\n// NewSealedSecretInformer constructs a new informer for SealedSecret type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewSealedSecretInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {\n\treturn NewFilteredSealedSecretInformer(client, namespace, resyncPeriod, indexers, nil)\n}\n\n// NewFilteredSealedSecretInformer constructs a new informer for SealedSecret type.\n// Always prefer using an informer factory to get a shared informer instead of getting an independent\n// one. This reduces memory footprint and number of connections to the server.\nfunc NewFilteredSealedSecretInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {\n\treturn cache.NewSharedIndexInformer(\n\t\t&cache.ListWatch{\n\t\t\tListFunc: func(options v1.ListOptions) (runtime.Object, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.BitnamiV1alpha1().SealedSecrets(namespace).List(context.TODO(), options)\n\t\t\t},\n\t\t\tWatchFunc: func(options v1.ListOptions) (watch.Interface, error) {\n\t\t\t\tif tweakListOptions != nil {\n\t\t\t\t\ttweakListOptions(&options)\n\t\t\t\t}\n\t\t\t\treturn client.BitnamiV1alpha1().SealedSecrets(namespace).Watch(context.TODO(), options)\n\t\t\t},\n\t\t},\n\t\t&sealedsecretsv1alpha1.SealedSecret{},\n\t\tresyncPeriod,\n\t\tindexers,\n\t)\n}\n\nfunc (f *sealedSecretInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {\n\treturn NewFilteredSealedSecretInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)\n}\n\nfunc (f *sealedSecretInformer) Informer() cache.SharedIndexInformer {\n\treturn f.factory.InformerFor(&sealedsecretsv1alpha1.SealedSecret{}, f.defaultInformer)\n}\n\nfunc (f *sealedSecretInformer) Lister() v1alpha1.SealedSecretLister {\n\treturn v1alpha1.NewSealedSecretLister(f.Informer().GetIndexer())\n}\n"
  },
  {
    "path": "pkg/client/listers/sealedsecrets/v1alpha1/expansion_generated.go",
    "content": "// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1alpha1\n\n// SealedSecretListerExpansion allows custom methods to be added to\n// SealedSecretLister.\ntype SealedSecretListerExpansion interface{}\n\n// SealedSecretNamespaceListerExpansion allows custom methods to be added to\n// SealedSecretNamespaceLister.\ntype SealedSecretNamespaceListerExpansion interface{}\n"
  },
  {
    "path": "pkg/client/listers/sealedsecrets/v1alpha1/sealedsecret.go",
    "content": "// Code generated by lister-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\tv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/apis/sealedsecrets/v1alpha1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/client-go/tools/cache\"\n)\n\n// SealedSecretLister helps list SealedSecrets.\n// All objects returned here must be treated as read-only.\ntype SealedSecretLister interface {\n\t// List lists all SealedSecrets in the indexer.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*v1alpha1.SealedSecret, err error)\n\t// SealedSecrets returns an object that can list and get SealedSecrets.\n\tSealedSecrets(namespace string) SealedSecretNamespaceLister\n\tSealedSecretListerExpansion\n}\n\n// sealedSecretLister implements the SealedSecretLister interface.\ntype sealedSecretLister struct {\n\tindexer cache.Indexer\n}\n\n// NewSealedSecretLister returns a new SealedSecretLister.\nfunc NewSealedSecretLister(indexer cache.Indexer) SealedSecretLister {\n\treturn &sealedSecretLister{indexer: indexer}\n}\n\n// List lists all SealedSecrets in the indexer.\nfunc (s *sealedSecretLister) List(selector labels.Selector) (ret []*v1alpha1.SealedSecret, err error) {\n\terr = cache.ListAll(s.indexer, selector, func(m interface{}) {\n\t\tret = append(ret, m.(*v1alpha1.SealedSecret))\n\t})\n\treturn ret, err\n}\n\n// SealedSecrets returns an object that can list and get SealedSecrets.\nfunc (s *sealedSecretLister) SealedSecrets(namespace string) SealedSecretNamespaceLister {\n\treturn sealedSecretNamespaceLister{indexer: s.indexer, namespace: namespace}\n}\n\n// SealedSecretNamespaceLister helps list and get SealedSecrets.\n// All objects returned here must be treated as read-only.\ntype SealedSecretNamespaceLister interface {\n\t// List lists all SealedSecrets in the indexer for a given namespace.\n\t// Objects returned here must be treated as read-only.\n\tList(selector labels.Selector) (ret []*v1alpha1.SealedSecret, err error)\n\t// Get retrieves the SealedSecret from the indexer for a given namespace and name.\n\t// Objects returned here must be treated as read-only.\n\tGet(name string) (*v1alpha1.SealedSecret, error)\n\tSealedSecretNamespaceListerExpansion\n}\n\n// sealedSecretNamespaceLister implements the SealedSecretNamespaceLister\n// interface.\ntype sealedSecretNamespaceLister struct {\n\tindexer   cache.Indexer\n\tnamespace string\n}\n\n// List lists all SealedSecrets in the indexer for a given namespace.\nfunc (s sealedSecretNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.SealedSecret, err error) {\n\terr = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {\n\t\tret = append(ret, m.(*v1alpha1.SealedSecret))\n\t})\n\treturn ret, err\n}\n\n// Get retrieves the SealedSecret from the indexer for a given namespace and name.\nfunc (s sealedSecretNamespaceLister) Get(name string) (*v1alpha1.SealedSecret, error) {\n\tobj, exists, err := s.indexer.GetByKey(s.namespace + \"/\" + name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !exists {\n\t\treturn nil, errors.NewNotFound(v1alpha1.Resource(\"sealedsecret\"), name)\n\t}\n\treturn obj.(*v1alpha1.SealedSecret), nil\n}\n"
  },
  {
    "path": "pkg/controller/controller.go",
    "content": "package controller\n\nimport (\n\t\"context\"\n\t\"crypto/rsa\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"reflect\"\n\t\"strings\"\n\t\"time\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapiequality \"k8s.io/apimachinery/pkg/api/equality\"\n\tk8serrors \"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/runtime/schema\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n\t\"k8s.io/apimachinery/pkg/util/wait\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\tv1 \"k8s.io/client-go/kubernetes/typed/core/v1\"\n\t\"k8s.io/client-go/tools/cache\"\n\t\"k8s.io/client-go/tools/record\"\n\t\"k8s.io/client-go/util/workqueue\"\n\t\"k8s.io/klog\"\n\n\t\"k8s.io/client-go/informers\"\n\n\tssv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/apis/sealedsecrets/v1alpha1\"\n\tssclientset \"github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned\"\n\tssscheme \"github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned/scheme\"\n\tssv1alpha1client \"github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned/typed/sealedsecrets/v1alpha1\"\n\tssinformer \"github.com/bitnami-labs/sealed-secrets/pkg/client/informers/externalversions\"\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/multidocyaml\"\n)\n\nconst (\n\t// SuccessUnsealed is used as part of the Event 'reason' when\n\t// a SealedSecret is unsealed successfully.\n\tSuccessUnsealed = \"Unsealed\"\n\n\t// ErrUpdateFailed is used as part of the Event 'reason' when\n\t// a SealedSecret fails to update the target Secret for a\n\t// non-cryptography reason. Typically this is due to API I/O\n\t// or RBAC issues.\n\tErrUpdateFailed = \"ErrUpdateFailed\"\n\n\t// ErrUnsealFailed is used as part of the Event 'reason' when a\n\t// SealedSecret fails the unsealing process.  Typically this\n\t// is because it is encrypted with the wrong key or has been\n\t// renamed from its original namespace/name.\n\tErrUnsealFailed = \"ErrUnsealFailed\"\n)\n\nvar (\n\t// ErrCast happens when a K8s any type cannot be casted to the expected type.\n\tErrCast = errors.New(\"cast error\")\n\n\tmaxRetries = 5\n)\n\n// Controller implements the main sealed-secrets-controller loop.\ntype Controller struct {\n\tqueue       workqueue.TypedRateLimitingInterface[string]\n\tssInformer  cache.SharedIndexInformer\n\tsInformer   cache.SharedIndexInformer\n\tkInformer   cache.SharedIndexInformer\n\tsclient     v1.SecretsGetter\n\tssclient    ssv1alpha1client.SealedSecretsGetter\n\trecorder    record.EventRecorder\n\tkeyRegistry *KeyRegistry\n\n\toldGCBehavior bool // feature flag to revert to old behavior where we delete the secrets instead of relying on owners reference.\n\tupdateStatus  bool // feature flag that enables updating the status subresource.\n}\n\n// NewController returns the main sealed-secrets controller loop.\nfunc NewController(\n\tclientset kubernetes.Interface,\n\tssclientset ssclientset.Interface,\n\tssinformer ssinformer.SharedInformerFactory,\n\tsinformer informers.SharedInformerFactory,\n\tkinformer informers.SharedInformerFactory,\n\tkeyRegistry *KeyRegistry,\n\tmaxRetriesConfig int,\n\tkeyOrderPriority string,\n) (*Controller, error) {\n\tqueue := workqueue.NewTypedRateLimitingQueue(workqueue.DefaultTypedControllerRateLimiter[string]())\n\n\tutilruntime.Must(ssscheme.AddToScheme(scheme.Scheme))\n\teventBroadcaster := record.NewBroadcaster()\n\teventBroadcaster.StartLogging(func(format string, args ...interface{}) {\n\t\t// Must use Sprintf to ensure slog doesn't interpret args... as key-value pairs\n\t\tslog.Info(fmt.Sprintf(format, args...))\n\t})\n\teventBroadcaster.StartRecordingToSink(&v1.EventSinkImpl{Interface: clientset.CoreV1().Events(\"\")})\n\trecorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: \"sealed-secrets\"})\n\n\tssInformer, err := watchSealedSecrets(ssinformer, queue)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar sInformer cache.SharedIndexInformer\n\tif sinformer != nil {\n\t\tsInformer, err = watchSecrets(sinformer, ssclientset, queue)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tvar kInformer cache.SharedIndexInformer\n\tif kinformer != nil {\n\t\tkInformer, err = watchKeySecrets(kinformer, keyRegistry, keyOrderPriority)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tmaxRetries = maxRetriesConfig\n\n\treturn &Controller{\n\t\tssInformer:  ssInformer,\n\t\tsInformer:   sInformer,\n\t\tkInformer:   kInformer,\n\t\tqueue:       queue,\n\t\tsclient:     clientset.CoreV1(),\n\t\tssclient:    ssclientset.BitnamiV1alpha1(),\n\t\trecorder:    recorder,\n\t\tkeyRegistry: keyRegistry,\n\t}, nil\n}\n\nfunc watchKeySecrets(kinformer informers.SharedInformerFactory, registry *KeyRegistry, keyOrderPriority string) (cache.SharedIndexInformer, error) {\n\tkInformer := kinformer.Core().V1().Secrets().Informer()\n\t_, err := kInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{\n\t\tAddFunc: func(obj interface{}) {\n\t\t\terr := registryNewKeyWithSecret(obj.(*corev1.Secret), registry, keyOrderPriority)\n\t\t\tif err != nil {\n\t\t\t\tslog.Error(\"failed to register key\", \"error\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not add event handler to secrets informer: %w\", err)\n\t}\n\treturn kInformer, nil\n}\n\nfunc watchSealedSecrets(ssinformer ssinformer.SharedInformerFactory, queue workqueue.TypedRateLimitingInterface[string]) (cache.SharedIndexInformer, error) {\n\tssInformer := ssinformer.Bitnami().V1alpha1().SealedSecrets().Informer()\n\t_, err := ssInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{\n\t\tAddFunc: func(obj interface{}) {\n\t\t\tkey, err := cache.MetaNamespaceKeyFunc(obj)\n\t\t\tif err == nil {\n\t\t\t\tqueue.Add(key)\n\t\t\t}\n\t\t},\n\t\tUpdateFunc: func(oldObj, newObj interface{}) {\n\t\t\tkey, err := cache.MetaNamespaceKeyFunc(newObj)\n\t\t\tif err == nil {\n\t\t\t\tif sealedSecretChanged(oldObj, newObj) {\n\t\t\t\t\tqueue.Add(key)\n\t\t\t\t} else {\n\t\t\t\t\tslog.Info(\"update suppressed, no changes in spec\", \"sealed-secret\", key)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tDeleteFunc: func(obj interface{}) {\n\t\t\tkey, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)\n\t\t\tif err == nil {\n\t\t\t\tqueue.Add(key)\n\t\t\t}\n\t\t\tif ssecret, ok := obj.(*ssv1alpha1.SealedSecret); ok {\n\t\t\t\tUnregisterCondition(ssecret)\n\t\t\t}\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not add event handler to sealed secrets informer: %w\", err)\n\t}\n\treturn ssInformer, nil\n}\n\nfunc sealedSecretChanged(oldObj, newObj interface{}) bool {\n\toldSealedSecret, err := convertSealedSecret(oldObj)\n\tif err != nil {\n\t\treturn true // any conversion error means we assume it might have changed\n\t}\n\tnewSealedSecret, err := convertSealedSecret(newObj)\n\tif err != nil {\n\t\treturn true\n\t}\n\treturn !reflect.DeepEqual(oldSealedSecret.Spec, newSealedSecret.Spec)\n}\n\nfunc watchSecrets(sinformer informers.SharedInformerFactory, ssclientset ssclientset.Interface, queue workqueue.TypedRateLimitingInterface[string]) (cache.SharedIndexInformer, error) {\n\tsInformer := sinformer.Core().V1().Secrets().Informer()\n\t_, err := sInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{\n\t\tDeleteFunc: func(obj interface{}) {\n\t\t\tskey, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)\n\t\t\tif err != nil {\n\t\t\t\tslog.Error(\"failed to fetch Secret key\", \"error\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tns, name, err := cache.SplitMetaNamespaceKey(skey)\n\t\t\tif err != nil {\n\t\t\t\tslog.Error(\"failed to get namespace and name from key\", \"secret\", skey, \"error\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tssecret, err := ssclientset.BitnamiV1alpha1().SealedSecrets(ns).Get(context.Background(), name, metav1.GetOptions{})\n\t\t\tif err != nil {\n\t\t\t\tif !k8serrors.IsNotFound(err) {\n\t\t\t\t\tslog.Error(\"failed to get SealedSecret\", \"secret\", skey, \"error\", err)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif !metav1.IsControlledBy(obj.(*corev1.Secret), ssecret) && !isAnnotatedToBeManaged(obj.(*corev1.Secret)) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tsskey, err := cache.MetaNamespaceKeyFunc(ssecret)\n\t\t\tif err != nil {\n\t\t\t\tslog.Error(\"failed to fetch SealedSecret key\", \"secret\", skey, \"error\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tqueue.Add(sskey)\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not add event handler to secrets informer: %w\", err)\n\t}\n\treturn sInformer, nil\n}\n\n// HasSynced returns true once this controller has completed an\n// initial resource listing.\nfunc (c *Controller) HasSynced() bool {\n\tvar synced bool\n\tif c.sInformer == nil {\n\t\tsynced = c.ssInformer.HasSynced()\n\t} else {\n\t\tsynced = c.ssInformer.HasSynced() && c.sInformer.HasSynced()\n\t}\n\treturn synced\n}\n\n// LastSyncResourceVersion is the resource version observed when last\n// synced with the underlying store. The value returned is not\n// synchronized with access to the underlying store and is not\n// thread-safe.\nfunc (c *Controller) LastSyncResourceVersion() string {\n\treturn c.ssInformer.LastSyncResourceVersion()\n}\n\n// Run begins processing items, and will continue until a value is\n// sent down stopCh.  It's an error to call Run more than once.  Run\n// blocks; call via go.\nfunc (c *Controller) Run(stopCh <-chan struct{}) {\n\tdefer utilruntime.HandleCrash()\n\n\tdefer c.queue.ShutDown()\n\n\tgo c.ssInformer.Run(stopCh)\n\tif c.sInformer != nil {\n\t\tgo c.sInformer.Run(stopCh)\n\t}\n\tif c.kInformer != nil {\n\t\tgo c.kInformer.Run(stopCh)\n\t}\n\n\tif !cache.WaitForCacheSync(stopCh, c.HasSynced) {\n\t\tutilruntime.HandleError(fmt.Errorf(\"timed out waiting for caches to sync\"))\n\t\treturn\n\t}\n\n\twait.Until(func() {\n\t\tc.runWorker(context.Background())\n\t}, time.Second, stopCh)\n\n\tslog.Error(\"Shutting down controller\")\n}\n\nfunc (c *Controller) runWorker(ctx context.Context) {\n\tfor c.processNextItem(ctx) {\n\t\t// continue looping\n\t}\n}\n\nfunc (c *Controller) processNextItem(ctx context.Context) bool {\n\tkey, quit := c.queue.Get()\n\tif quit {\n\t\treturn false\n\t}\n\n\tdefer c.queue.Done(key)\n\terr := c.unseal(ctx, key)\n\tif err == nil {\n\t\t// No error, reset the ratelimit counters\n\t\tc.queue.Forget(key)\n\t} else if isImmutableError(err) {\n\t\t// Do not retry updating immutable fields of an immutable secret\n\t\tslog.Error(formatImmutableError(key))\n\t\tc.queue.Forget(key)\n\t\tutilruntime.HandleError(err)\n\t} else if c.queue.NumRequeues(key) < maxRetries {\n\t\tslog.Error(\"Error updating, will retry\", \"key\", key, \"error\", err)\n\t\tc.queue.AddRateLimited(key)\n\t} else {\n\t\t// err != nil and too many retries\n\t\tslog.Error(\"Error updating, giving up\", \"key\", key, \"error\", err)\n\t\tc.queue.Forget(key)\n\t\tutilruntime.HandleError(err)\n\t}\n\n\treturn true\n}\n\nfunc (c *Controller) unseal(ctx context.Context, key string) (unsealErr error) {\n\tunsealRequestsTotal.Inc()\n\tobj, exists, err := c.ssInformer.GetIndexer().GetByKey(key)\n\tif err != nil {\n\t\tslog.Error(\"Error fetching object from store\", \"key\", key, \"error\", err)\n\t\tunsealErrorsTotal.WithLabelValues(\"fetch\", \"\").Inc()\n\t\treturn err\n\t}\n\n\tif !exists {\n\t\t// the dependent secret will be GC: by k8s itself, see:\n\t\t// https://kubernetes.io/docs/concepts/workloads/controllers/garbage-collection/#owners-and-dependents\n\n\t\t// TODO: remove this feature flag in a subsequent release.\n\t\tif c.oldGCBehavior {\n\t\t\tslog.Info(\"SealedSecret has gone, deleting Secret\", \"sealed-secret\", key)\n\t\t\tns, name, err := cache.SplitMetaNamespaceKey(key)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terr = c.sclient.Secrets(ns).Delete(ctx, name, metav1.DeleteOptions{})\n\t\t\tif err != nil && !k8serrors.IsNotFound(err) {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\tssecret, err := convertSealedSecret(obj)\n\tif err != nil {\n\t\treturn err\n\t}\n\tslog.Info(\"Updating\", \"key\", key)\n\n\t// any exit of this function at this point will cause an update to the status subresource\n\t// of the SealedSecret custom resource. The return value of the unseal function is available\n\t// to the deferred function body in the unsealErr named return value (even if explicit return\n\t// statements are used to return).\n\tdefer func(ctx context.Context) {\n\t\tif err := c.updateSealedSecretStatus(ctx, ssecret, unsealErr); err != nil {\n\t\t\t// Non-fatal.  Log and continue.\n\t\t\tslog.Error(\"Error updating SealedSecret status\", \"sealed-secret\", key, \"error\", err)\n\t\t\tunsealErrorsTotal.WithLabelValues(\"status\", ssecret.GetNamespace()).Inc()\n\t\t} else {\n\t\t\tObserveCondition(ssecret)\n\t\t}\n\t}(ctx)\n\n\tnewSecret, err := c.attemptUnseal(ssecret)\n\tif err != nil {\n\t\tc.recorder.Eventf(ssecret, corev1.EventTypeWarning, ErrUnsealFailed, \"Failed to unseal: %v\", err)\n\t\tunsealErrorsTotal.WithLabelValues(\"unseal\", ssecret.GetNamespace()).Inc()\n\t\treturn err\n\t}\n\n\tsecret, err := c.sclient.Secrets(ssecret.GetObjectMeta().GetNamespace()).Get(ctx, newSecret.GetObjectMeta().GetName(), metav1.GetOptions{})\n\tif k8serrors.IsNotFound(err) {\n\t\tsecret, err = c.sclient.Secrets(ssecret.GetObjectMeta().GetNamespace()).Create(ctx, newSecret, metav1.CreateOptions{})\n\t}\n\tif err != nil {\n\t\tc.recorder.Event(ssecret, corev1.EventTypeWarning, ErrUpdateFailed, err.Error())\n\t\tunsealErrorsTotal.WithLabelValues(\"update\", ssecret.GetNamespace()).Inc()\n\t\treturn err\n\t}\n\n\tif !metav1.IsControlledBy(secret, ssecret) && !isAnnotatedToBeManaged(secret) && !isAnnotatedToBePatched(secret) {\n\t\tmsg := fmt.Sprintf(\"Resource %q already exists and is not managed by SealedSecret\", secret.Name)\n\t\tc.recorder.Event(ssecret, corev1.EventTypeWarning, ErrUpdateFailed, msg)\n\t\tunsealErrorsTotal.WithLabelValues(\"unmanaged\", ssecret.GetNamespace()).Inc()\n\t\treturn fmt.Errorf(\"failed update: %s\", msg)\n\t}\n\n\torigSecret := secret\n\tsecret = secret.DeepCopy()\n\n\tif isAnnotatedToBePatched(secret) {\n\t\tif secret.Data == nil {\n\t\t\tsecret.Data = make(map[string][]byte)\n\t\t}\n\n\t\tfor k, v := range newSecret.Data {\n\t\t\tsecret.Data[k] = v\n\t\t}\n\n\t\tif secret.ObjectMeta.Labels == nil {\n\t\t\tsecret.ObjectMeta.Labels = make(map[string]string)\n\t\t}\n\n\t\tfor k, v := range newSecret.ObjectMeta.Labels {\n\t\t\tsecret.ObjectMeta.Labels[k] = v\n\t\t}\n\n\t\tfor k, v := range newSecret.ObjectMeta.Annotations {\n\t\t\tsecret.ObjectMeta.Annotations[k] = v\n\t\t}\n\n\t\tif isAnnotatedToBeManaged(secret) {\n\t\t\tsecret.ObjectMeta.OwnerReferences = newSecret.ObjectMeta.OwnerReferences\n\t\t}\n\t} else {\n\t\tsecret.Data = newSecret.Data\n\t\tsecret.ObjectMeta.Annotations = newSecret.ObjectMeta.Annotations\n\t\tsecret.ObjectMeta.Labels = newSecret.ObjectMeta.Labels\n\t\tsecret.ObjectMeta.OwnerReferences = newSecret.ObjectMeta.OwnerReferences\n\t}\n\n\tsecret.Type = newSecret.Type\n\n\tif !apiequality.Semantic.DeepEqual(origSecret, secret) {\n\t\t_, err = c.sclient.Secrets(ssecret.GetObjectMeta().GetNamespace()).Update(ctx, secret, metav1.UpdateOptions{})\n\t\tif err != nil {\n\t\t\tvar message = err.Error()\n\t\t\tif isImmutableError(err) {\n\t\t\t\tmessage = formatImmutableError(key)\n\t\t\t}\n\n\t\t\tc.recorder.Event(ssecret, corev1.EventTypeWarning, ErrUpdateFailed, message)\n\t\t\tunsealErrorsTotal.WithLabelValues(\"update\", ssecret.GetNamespace()).Inc()\n\t\t\treturn err\n\t\t}\n\t}\n\n\tc.recorder.Event(ssecret, corev1.EventTypeNormal, SuccessUnsealed, \"SealedSecret unsealed successfully\")\n\treturn nil\n}\n\nfunc convertSealedSecret(obj any) (*ssv1alpha1.SealedSecret, error) {\n\tsealedSecret, ok := (obj).(*ssv1alpha1.SealedSecret)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%w: failed to cast %v into SealedSecret\", ErrCast, obj)\n\t}\n\tif sealedSecret.APIVersion == \"\" || sealedSecret.Kind == \"\" {\n\t\t// https://github.com/operator-framework/operator-sdk/issues/727\n\t\tgv := schema.GroupVersion{Group: ssv1alpha1.GroupName, Version: \"v1alpha1\"}\n\t\tgvk := gv.WithKind(\"SealedSecret\")\n\t\tsealedSecret.APIVersion = gvk.GroupVersion().String()\n\t\tsealedSecret.Kind = gvk.Kind\n\t}\n\treturn sealedSecret, nil\n}\n\nfunc (c *Controller) updateSealedSecretStatus(ctx context.Context, ssecret *ssv1alpha1.SealedSecret, unsealError error) error {\n\tif !c.updateStatus {\n\t\tklog.V(2).Infof(\"not updating status because updateStatus feature flag not turned on\")\n\t\treturn nil\n\t}\n\n\tif ssecret.Status == nil {\n\t\tssecret.Status = &ssv1alpha1.SealedSecretStatus{}\n\t}\n\n\tupdatedRequired := updateSealedSecretsStatusConditions(ssecret.Status, unsealError)\n\tif updatedRequired || (ssecret.Status.ObservedGeneration != ssecret.ObjectMeta.Generation) {\n\t\tssecret.Status.ObservedGeneration = ssecret.ObjectMeta.Generation\n\t\t_, err := c.ssclient.SealedSecrets(ssecret.GetObjectMeta().GetNamespace()).UpdateStatus(ctx, ssecret, metav1.UpdateOptions{})\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc updateSealedSecretsStatusConditions(st *ssv1alpha1.SealedSecretStatus, unsealError error) bool {\n\tvar updateRequired bool\n\tcond := func() *ssv1alpha1.SealedSecretCondition {\n\t\tfor i := range st.Conditions {\n\t\t\tif st.Conditions[i].Type == ssv1alpha1.SealedSecretSynced {\n\t\t\t\treturn &st.Conditions[i]\n\t\t\t}\n\t\t}\n\t\tst.Conditions = append(st.Conditions, ssv1alpha1.SealedSecretCondition{\n\t\t\tType: ssv1alpha1.SealedSecretSynced,\n\t\t})\n\t\treturn &st.Conditions[len(st.Conditions)-1]\n\t}()\n\n\tvar status corev1.ConditionStatus\n\tif unsealError == nil {\n\t\tstatus = corev1.ConditionTrue\n\t\tcond.Message = \"\"\n\t} else {\n\t\tstatus = corev1.ConditionFalse\n\t\tcond.Message = unsealError.Error()\n\t}\n\n\tcond.LastUpdateTime = metav1.Now()\n\t// Status has changed, update the transition time and signal that an update is required\n\tif cond.Status != status {\n\t\tcond.LastTransitionTime = cond.LastUpdateTime\n\t\tcond.Status = status\n\t\tupdateRequired = true\n\t}\n\n\treturn updateRequired\n}\n\nfunc isAnnotatedToBeManaged(secret *corev1.Secret) bool {\n\treturn secret.Annotations[ssv1alpha1.SealedSecretManagedAnnotation] == \"true\"\n}\n\nfunc isAnnotatedToBePatched(secret *corev1.Secret) bool {\n\treturn secret.Annotations[ssv1alpha1.SealedSecretPatchAnnotation] == \"true\"\n}\n\nfunc isImmutableError(err error) bool {\n\treturn strings.HasSuffix(err.Error(), \"field is immutable when `immutable` is set\")\n}\n\nfunc formatImmutableError(key string) string {\n\treturn fmt.Sprintf(\"Error updating %s: the target Secret is immutable. Once a Secret is marked as immutable, it is not possible to revert this change nor to mutate the contents of the data field. You can only delete and recreate the Secret.\", key)\n}\n\n// AttemptUnseal tries to unseal a secret.\nfunc (c *Controller) AttemptUnseal(content []byte) (bool, error) {\n\tif err := multidocyaml.EnsureNotMultiDoc(content); err != nil {\n\t\treturn false, err\n\t}\n\n\tobject, err := runtime.Decode(scheme.Codecs.UniversalDecoder(ssv1alpha1.SchemeGroupVersion), content)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tswitch s := object.(type) {\n\tcase *ssv1alpha1.SealedSecret:\n\t\tif _, err := c.attemptUnseal(s); err != nil {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn true, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected resource type: %s\", s.GetObjectKind().GroupVersionKind().String())\n\t}\n}\n\n// Rotate takes a sealed secret and returns a sealed secret that has been encrypted\n// with the latest private key. If the secret is already encrypted with the latest,\n// returns the input.\nfunc (c *Controller) Rotate(content []byte) ([]byte, error) {\n\tobject, err := runtime.Decode(scheme.Codecs.UniversalDecoder(ssv1alpha1.SchemeGroupVersion), content)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch s := object.(type) {\n\tcase *ssv1alpha1.SealedSecret:\n\t\t// Verify metainformation is well set up in Template ObjectMeta and ObjectMeta to avoid unconsistences with the scope during the rotate.\n\t\t// This is going to keep the original scope.\n\t\tif !reflect.DeepEqual(s.ObjectMeta, s.Spec.Template.ObjectMeta) {\n\t\t\ts.ObjectMeta.DeepCopyInto(&s.Spec.Template.ObjectMeta)\n\t\t\tslog.Warn(\"Sealed Secret metadata doesn't match. Please align your Sealed Secret metadata\")\n\t\t}\n\n\t\tsecret, err := c.attemptUnseal(s)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error decrypting secret. %v\", err)\n\t\t}\n\t\tlatestPrivKey := c.keyRegistry.latestPrivateKey()\n\t\tresealedSecret, err := ssv1alpha1.NewSealedSecret(scheme.Codecs, &latestPrivKey.PublicKey, secret)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error creating new sealed secret. %v\", err)\n\t\t}\n\t\tdata, err := json.Marshal(resealedSecret)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error marshalling new secret to json. %v\", err)\n\t\t}\n\t\treturn data, nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unexpected resource type: %s\", s.GetObjectKind().GroupVersionKind().String())\n\t}\n}\n\nfunc (c *Controller) attemptUnseal(ss *ssv1alpha1.SealedSecret) (*corev1.Secret, error) {\n\treturn attemptUnseal(ss, c.keyRegistry)\n}\n\nfunc attemptUnseal(ss *ssv1alpha1.SealedSecret, keyRegistry *KeyRegistry) (*corev1.Secret, error) {\n\tprivateKeys := map[string]*rsa.PrivateKey{}\n\tfor k, v := range keyRegistry.keys {\n\t\tprivateKeys[k] = v.private\n\t}\n\treturn ss.Unseal(scheme.Codecs, privateKeys)\n}\n"
  },
  {
    "path": "pkg/controller/controller_test.go",
    "content": "package controller\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"encoding/json\"\n\n\tssv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/apis/sealedsecrets/v1alpha1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/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/fake\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\n\tssfake \"github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned/fake\"\n)\n\nfunc TestIsAnnotatedToBePatched(t *testing.T) {\n\ttests := []struct {\n\t\tannotations map[string]string\n\t\twant        bool\n\t}{\n\t\t{annotations: map[string]string{ssv1alpha1.SealedSecretPatchAnnotation: \"true\"}, want: true},\n\t\t{annotations: map[string]string{ssv1alpha1.SealedSecretPatchAnnotation: \"TRUE\"}, want: false},\n\t\t{annotations: map[string]string{ssv1alpha1.SealedSecretPatchAnnotation: \"false\"}, want: false},\n\t\t{annotations: map[string]string{ssv1alpha1.SealedSecretPatchAnnotation: \"\"}, want: false},\n\t\t{annotations: map[string]string{\"something\": \"else\"}, want: false},\n\t\t{annotations: map[string]string{}, want: false},\n\t}\n\n\tfor i, tc := range tests {\n\t\ts := &corev1.Secret{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tNamespace:   \"test-ns\",\n\t\t\t\tName:        \"test-secret\",\n\t\t\t\tAnnotations: tc.annotations,\n\t\t\t},\n\t\t\tData: map[string][]byte{\n\t\t\t\t\"foo\": []byte(\"bar\"),\n\t\t\t},\n\t\t}\n\n\t\tgot := isAnnotatedToBePatched(s)\n\t\tif got != tc.want {\n\t\t\tt.Fatalf(\"test %d: expected: %v, got: %v\", i+1, tc.want, got)\n\t\t}\n\t}\n}\n\nfunc TestIsAnnotatedToBeManaged(t *testing.T) {\n\ttests := []struct {\n\t\tannotations map[string]string\n\t\twant        bool\n\t}{\n\t\t{annotations: map[string]string{ssv1alpha1.SealedSecretManagedAnnotation: \"true\"}, want: true},\n\t\t{annotations: map[string]string{ssv1alpha1.SealedSecretManagedAnnotation: \"TRUE\"}, want: false},\n\t\t{annotations: map[string]string{ssv1alpha1.SealedSecretManagedAnnotation: \"false\"}, want: false},\n\t\t{annotations: map[string]string{ssv1alpha1.SealedSecretManagedAnnotation: \"\"}, want: false},\n\t\t{annotations: map[string]string{\"something\": \"else\"}, want: false},\n\t\t{annotations: map[string]string{}, want: false},\n\t}\n\n\tfor i, tc := range tests {\n\t\ts := &corev1.Secret{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tNamespace:   \"test-ns\",\n\t\t\t\tName:        \"test-secret\",\n\t\t\t\tAnnotations: tc.annotations,\n\t\t\t},\n\t\t\tData: map[string][]byte{\n\t\t\t\t\"foo\": []byte(\"bar\"),\n\t\t\t},\n\t\t}\n\n\t\tgot := isAnnotatedToBeManaged(s)\n\t\tif got != tc.want {\n\t\t\tt.Fatalf(\"test %d: expected: %v, got: %v\", i+1, tc.want, got)\n\t\t}\n\t}\n}\n\nfunc TestConvert2SealedSecretBadType(t *testing.T) {\n\tobj := struct{}{}\n\t_, got := convertSealedSecret(obj)\n\twant := ErrCast\n\tif !errors.Is(got, want) {\n\t\tt.Fatalf(\"got %v want %v\", got, want)\n\t}\n}\n\nfunc TestConvert2SealedSecretFills(t *testing.T) {\n\tsealedSecret := ssv1alpha1.SealedSecret{}\n\n\tresult, err := convertSealedSecret(any(&sealedSecret))\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected failure converting to a sealed secret: %v\", err)\n\t}\n\tgot := fmt.Sprintf(\"%s %s\", result.APIVersion, result.Kind)\n\twant := \"bitnami.com/v1alpha1 SealedSecret\"\n\tif got != want {\n\t\tt.Fatalf(\"got %q want %q\", got, want)\n\t}\n}\n\nfunc TestConvert2SealedSecretPassThrough(t *testing.T) {\n\tsealedSecret := ssv1alpha1.SealedSecret{}\n\tsealedSecret.APIVersion = \"bitnami.com/v1alpha1\"\n\tsealedSecret.Kind = \"SealedSecrets\"\n\n\twant := &sealedSecret\n\tgot, err := convertSealedSecret(any(want))\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected failure converting to a sealed secret: %v\", err)\n\t}\n\tif got != want {\n\t\tt.Fatalf(\"got %v want %v\", got, want)\n\t}\n}\n\nfunc TestDefaultConfigDoesNotSkipRecreate(t *testing.T) {\n\tns := \"some-namespace\"\n\tkeyNs := \"some-key-namespace\"\n\tvar tweakopts func(*metav1.ListOptions)\n\tclientset := fake.NewClientset()\n\tssc := ssfake.NewSimpleClientset()\n\tkeyRegistry := testKeyRegister(t, context.Background(), clientset, ns)\n\n\tgot, err := prepareController(clientset, ns, keyNs, tweakopts, &Flags{SkipRecreate: false}, ssc, keyRegistry)\n\tif err != nil {\n\t\tt.Fatalf(\"err %v want %v\", got, nil)\n\t}\n\tif got == nil {\n\t\tt.Fatalf(\"ctrl %v want non nil\", got)\n\t}\n\tif got.sInformer == nil {\n\t\tt.Fatalf(\"sInformer %v want non nil\", got.sInformer)\n\t}\n}\n\nfunc TestSkipRecreateConfigDoesSkipIt(t *testing.T) {\n\tns := \"some-namespace\"\n\tkeyNs := \"some-key-namespace\"\n\tvar tweakopts func(*metav1.ListOptions)\n\tclientset := fake.NewClientset()\n\tssc := ssfake.NewSimpleClientset()\n\tkeyRegistry := testKeyRegister(t, context.Background(), clientset, ns)\n\n\tgot, err := prepareController(clientset, ns, keyNs, tweakopts, &Flags{SkipRecreate: true}, ssc, keyRegistry)\n\tif err != nil {\n\t\tt.Fatalf(\"err %v want %v\", got, nil)\n\t}\n\tif got == nil {\n\t\tt.Fatalf(\"ctrl %v want non nil\", got)\n\t}\n\tif got.sInformer != nil {\n\t\tt.Fatalf(\"sInformer %v want nil\", got.sInformer)\n\t}\n}\n\nfunc TestEmptyStatusSendsUpdate(t *testing.T) {\n\tupdateRequired := updateSealedSecretsStatusConditions(&ssv1alpha1.SealedSecretStatus{}, nil)\n\n\tif !updateRequired {\n\t\tt.Fatalf(\"expected status update, but no update was send\")\n\t}\n}\n\nfunc TestStatusUpdateSendsUpdate(t *testing.T) {\n\tstatus := &ssv1alpha1.SealedSecretStatus{\n\t\tConditions: []ssv1alpha1.SealedSecretCondition{{\n\t\t\tStatus:         \"False\",\n\t\t\tType:           ssv1alpha1.SealedSecretSynced,\n\t\t\tLastUpdateTime: metav1.Now(),\n\t\t}},\n\t}\n\tupdateRequired := updateSealedSecretsStatusConditions(status, nil)\n\n\tif !updateRequired {\n\t\tt.Fatalf(\"expected status update, but no update was send\")\n\t}\n\n\tif status.Conditions[0].LastTransitionTime.IsZero() {\n\t\tt.Fatalf(\"expected LastTransitionTime is not empty\")\n\t}\n\n\tif status.Conditions[0].LastUpdateTime.IsZero() {\n\t\tt.Fatalf(\"expected LastUpdateTime is not empty\")\n\t}\n}\n\nfunc TestSameStatusNoUpdate(t *testing.T) {\n\tupdateRequired := updateSealedSecretsStatusConditions(&ssv1alpha1.SealedSecretStatus{\n\t\tConditions: []ssv1alpha1.SealedSecretCondition{{\n\t\t\tType:   ssv1alpha1.SealedSecretSynced,\n\t\t\tStatus: \"False\",\n\t\t}},\n\t}, errors.New(\"testerror\"))\n\n\tif updateRequired {\n\t\tt.Fatalf(\"expected no status update, but update was send\")\n\t}\n}\n\nfunc TestSyncedSecretWithErrorSendsUpdate(t *testing.T) {\n\tupdateRequired := updateSealedSecretsStatusConditions(&ssv1alpha1.SealedSecretStatus{\n\t\tConditions: []ssv1alpha1.SealedSecretCondition{{\n\t\t\tType:   ssv1alpha1.SealedSecretSynced,\n\t\t\tStatus: \"True\",\n\t\t}},\n\t}, errors.New(\"testerror\"))\n\n\tif !updateRequired {\n\t\tt.Fatalf(\"expected status update, but no update was send\")\n\t}\n}\n\nfunc testKeyRegister(t *testing.T, ctx context.Context, clientset kubernetes.Interface, ns string) *KeyRegistry {\n\tt.Helper()\n\n\tkeyLabel := SealedSecretsKeyLabel\n\tprefix := \"test-keys\"\n\ttestKeySize := 4096\n\tkeyRegistry, err := initKeyRegistry(ctx, clientset, rand.Reader, ns, prefix, keyLabel, testKeySize, \"CertNotBefore\")\n\tif err != nil {\n\t\tt.Fatalf(\"failed to provision key registry: %v\", err)\n\t}\n\treturn keyRegistry\n}\n\nfunc prettyEncoder(codecs runtimeserializer.CodecFactory, mediaType string, gv runtime.GroupVersioner) (runtime.Encoder, error) {\n\tinfo, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"binary can't serialize %s\", mediaType)\n\t}\n\n\tprettyEncoder := info.PrettySerializer\n\tif prettyEncoder == nil {\n\t\tprettyEncoder = info.Serializer\n\t}\n\n\tenc := codecs.EncoderForVersion(prettyEncoder, gv)\n\treturn enc, nil\n}\n\nfunc TestRotate(t *testing.T) {\n\tns := \"some-namespace\"\n\tkeyNs := \"some-key-namespace\"\n\tvar tweakopts func(*metav1.ListOptions)\n\tclientset := fake.NewClientset()\n\tssc := ssfake.NewSimpleClientset()\n\tkeyRegistry := testKeyRegister(t, context.Background(), clientset, ns)\n\n\t// Add a key to the controller for second test\n\tvalidFor := time.Hour\n\tcn := \"my-cn\"\n\t_, err := keyRegistry.generateKey(context.Background(), validFor, cn, \"\", \"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcontroller, err := prepareController(clientset, ns, keyNs, tweakopts, &Flags{SkipRecreate: false}, ssc, keyRegistry)\n\tif err != nil {\n\t\tt.Fatalf(\"err %v want %v\", err, nil)\n\t}\n\tif controller == nil {\n\t\tt.Fatalf(\"ctrl %v want non nil\", controller)\n\t}\n\tif controller.sInformer == nil {\n\t\tt.Fatalf(\"sInformer %v want non nil\", controller.sInformer)\n\t}\n\n\tsecret := &corev1.Secret{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tAPIVersion: \"v1\",\n\t\t\tKind:       \"Secret\",\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"ss\",\n\t\t\tNamespace: \"default\",\n\t\t},\n\t\tData: map[string][]byte{\n\t\t\t// dGVtcG9yYWw= is base64 for \"temporal\"\n\t\t\t\"password\": []byte(\"temporal\"),\n\t\t},\n\t}\n\n\tcert, err := controller.keyRegistry.getCert()\n\tif err != nil {\n\t\tt.Fatalf(\"error getting certificate: %v\", err)\n\t}\n\n\tssecret, err := ssv1alpha1.NewSealedSecret(scheme.Codecs, cert.PublicKey.(*rsa.PublicKey), secret)\n\tif err != nil {\n\t\tt.Fatalf(\"error creating sealed secrets: %v\", err)\n\t}\n\n\tprettyEnc, err := prettyEncoder(scheme.Codecs, runtime.ContentTypeYAML, ssv1alpha1.SchemeGroupVersion)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected pretty encoding: %v\", err)\n\t}\n\n\tdata, err := runtime.Encode(prettyEnc, ssecret)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected encoding the sealed secret: %v\", err)\n\t}\n\n\tgot, err := controller.Rotate(data)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected failure converting to a sealed secret: %v\", err)\n\t}\n\tif string(got) == string(data) {\n\t\tt.Fatalf(\"got %v want %v\", string(got), string(data))\n\t}\n}\n\nfunc TestRotateKeepScope(t *testing.T) {\n\tns := \"some-namespace\"\n\tkeyNs := \"some-key-namespace\"\n\tvar tweakopts func(*metav1.ListOptions)\n\tclientset := fake.NewClientset()\n\tssc := ssfake.NewSimpleClientset()\n\tkeyRegistry := testKeyRegister(t, context.Background(), clientset, ns)\n\n\t// Add a key to the controller for second test\n\tvalidFor := time.Hour\n\tcn := \"my-cn\"\n\t_, err := keyRegistry.generateKey(context.Background(), validFor, cn, \"\", \"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcontroller, err := prepareController(clientset, ns, keyNs, tweakopts, &Flags{SkipRecreate: false}, ssc, keyRegistry)\n\tif err != nil {\n\t\tt.Fatalf(\"err %v want %v\", err, nil)\n\t}\n\tif controller == nil {\n\t\tt.Fatalf(\"ctrl %v want non nil\", controller)\n\t}\n\tif controller.sInformer == nil {\n\t\tt.Fatalf(\"sInformer %v want non nil\", controller.sInformer)\n\t}\n\n\tsecret := &corev1.Secret{\n\t\tTypeMeta: metav1.TypeMeta{\n\t\t\tAPIVersion: \"v1\",\n\t\t\tKind:       \"Secret\",\n\t\t},\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"ss\",\n\t\t\tNamespace: \"default\",\n\t\t},\n\t\tData: map[string][]byte{\n\t\t\t// dGVtcG9yYWw= is base64 for \"temporal\"\n\t\t\t\"password\": []byte(\"temporal\"),\n\t\t},\n\t}\n\n\tcert, err := controller.keyRegistry.getCert()\n\tif err != nil {\n\t\tt.Fatalf(\"error getting certificate: %v\", err)\n\t}\n\n\tssecret, err := ssv1alpha1.NewSealedSecret(scheme.Codecs, cert.PublicKey.(*rsa.PublicKey), secret)\n\tif err != nil {\n\t\tt.Fatalf(\"error creating sealed secrets: %v\", err)\n\t}\n\tssecret.Spec.Template.ObjectMeta.Annotations = map[string]string{ssv1alpha1.SealedSecretClusterWideAnnotation: \"true\"}\n\n\tprettyEnc, err := prettyEncoder(scheme.Codecs, runtime.ContentTypeJSON, ssv1alpha1.SchemeGroupVersion)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected pretty encoding: %v\", err)\n\t}\n\n\tdata, err := runtime.Encode(prettyEnc, ssecret)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected encoding the sealed secret: %v\", err)\n\t}\n\n\tout, err := controller.Rotate(data)\n\tif err != nil {\n\t\tt.Fatalf(\"expected failure is not hit\")\n\t}\n\n\ts := &ssv1alpha1.SealedSecret{}\n\tif err = json.Unmarshal(out, s); err != nil {\n\t\tt.Fatalf(\"error unmarshalling the rotate sealed secret\")\n\t}\n\n\tif ssv1alpha1.SecretScope(s) != ssv1alpha1.SecretScope(ssecret) {\n\t\tt.Fatalf(\"Scope from the original and the rotate sealed secret do not match\")\n\t}\n}\n"
  },
  {
    "path": "pkg/controller/funcs.go",
    "content": "package controller\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n)\n\n// ScheduleJobWithTrigger creates a long-running loop that runs a job after an initialDelay\n// and then after each period duration.\n// It returns a trigger function that runs the job early when called.\nfunc ScheduleJobWithTrigger(initialDelay, period time.Duration, job func()) func() {\n\ttrigger := make(chan struct{})\n\tgo func() {\n\t\tfor {\n\t\t\t<-trigger\n\t\t\tjob()\n\t\t}\n\t}()\n\tgo func() {\n\t\ttime.Sleep(initialDelay)\n\t\tfor {\n\t\t\ttrigger <- struct{}{}\n\t\t\ttime.Sleep(period)\n\t\t}\n\t}()\n\treturn func() {\n\t\ttrigger <- struct{}{}\n\t}\n}\n\nconst (\n\tkubeChars     = \"abcdefghijklmnopqrstuvwxyz0123456789-\" // Acceptable characters in k8s resource name\n\tmaxNameLength = 245                                     // Max resource name length is 253, leave some room for a suffix\n)\n\nfunc validateKeyPrefix(name string) (string, error) {\n\tif len(name) > maxNameLength {\n\t\treturn \"\", fmt.Errorf(\"name is too long, must be shorter than %d, got %d\", maxNameLength, len(name))\n\t}\n\tfor _, char := range name {\n\t\tif !strings.ContainsRune(kubeChars, char) {\n\t\t\treturn \"\", fmt.Errorf(\"name contains illegal character %c\", char)\n\t\t}\n\t}\n\treturn name, nil\n}\n\nfunc removeDuplicates(strSlice []string) []string {\n\tallKeys := make(map[string]bool)\n\tlist := []string{}\n\tfor _, item := range strSlice {\n\t\tif _, value := allKeys[item]; !value {\n\t\t\tallKeys[item] = true\n\t\t\tlist = append(list, item)\n\t\t}\n\t}\n\treturn list\n}\n"
  },
  {
    "path": "pkg/controller/keyregistry.go",
    "content": "package controller\n\nimport (\n\t\"context\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/crypto\"\n\t\"k8s.io/client-go/kubernetes\"\n\tcertUtil \"k8s.io/client-go/util/cert\"\n)\n\n// A Key holds the cryptographic key pair and some metadata about it.\ntype Key struct {\n\tprivate      *rsa.PrivateKey\n\tcert         *x509.Certificate\n\tfingerprint  string\n\torderingTime time.Time\n}\n\n// A KeyRegistry manages the key pairs used to (un)seal secrets.\ntype KeyRegistry struct {\n\tsync.Mutex\n\tclient        kubernetes.Interface\n\tnamespace     string\n\tkeyPrefix     string\n\tkeyLabel      string\n\tkeysize       int\n\tkeys          map[string]*Key\n\tmostRecentKey *Key\n}\n\n// NewKeyRegistry creates a new KeyRegistry.\nfunc NewKeyRegistry(client kubernetes.Interface, namespace, keyPrefix, keyLabel string, keysize int) *KeyRegistry {\n\treturn &KeyRegistry{\n\t\tclient:    client,\n\t\tnamespace: namespace,\n\t\tkeyPrefix: keyPrefix,\n\t\tkeysize:   keysize,\n\t\tkeyLabel:  keyLabel,\n\t\tkeys:      map[string]*Key{},\n\t}\n}\n\nfunc (kr *KeyRegistry) generateKey(ctx context.Context, validFor time.Duration, cn string, privateKeyAnnotations string, privateKeyLabels string) (string, error) {\n\tkey, cert, err := generatePrivateKeyAndCert(kr.keysize, validFor, cn)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tcerts := []*x509.Certificate{cert}\n\tgeneratedName, err := writeKey(ctx, kr.client, key, certs, kr.namespace, kr.keyLabel, kr.keyPrefix, privateKeyAnnotations, privateKeyLabels)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\t// Only store key to local store if write to k8s worked\n\tif err := kr.registerNewKey(generatedName, key, cert, time.Now()); err != nil {\n\t\treturn \"\", err\n\t}\n\tslog.Info(\"New key written\", \"namespace\", kr.namespace, \"name\", generatedName)\n\tslog.Info(\"Certificate generated\", \"certificate\", pem.EncodeToMemory(&pem.Block{Type: certUtil.CertificateBlockType, Bytes: cert.Raw}))\n\treturn generatedName, nil\n}\n\nfunc (kr *KeyRegistry) registerNewKey(keyName string, privKey *rsa.PrivateKey, cert *x509.Certificate, orderingTime time.Time) error {\n\tfingerprint, err := crypto.PublicKeyFingerprint(&privKey.PublicKey)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tk := &Key{\n\t\tprivate:      privKey,\n\t\tcert:         cert,\n\t\tfingerprint:  fingerprint,\n\t\torderingTime: orderingTime,\n\t}\n\tkr.keys[k.fingerprint] = k\n\n\tif kr.mostRecentKey == nil || kr.mostRecentKey.orderingTime.Before(orderingTime) {\n\t\tkr.mostRecentKey = k\n\t}\n\n\treturn nil\n}\n\nfunc (kr *KeyRegistry) latestPrivateKey() *rsa.PrivateKey {\n\treturn kr.mostRecentKey.private\n}\n\n// getCert returns the current certificate. This method can be called by another goroutine.\nfunc (kr *KeyRegistry) getCert() (*x509.Certificate, error) {\n\tkr.Lock()\n\tdefer kr.Unlock()\n\n\tif kr.mostRecentKey == nil {\n\t\treturn nil, fmt.Errorf(\"key registry has no keys\")\n\t}\n\treturn kr.mostRecentKey.cert, nil\n}\n"
  },
  {
    "path": "pkg/controller/keyregistry_test.go",
    "content": "package controller\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestRegisterNewKey(t *testing.T) {\n\tconst keySize = 2048\n\tvalidFor := time.Hour\n\tcn := \"my-cn\"\n\tkr := NewKeyRegistry(nil, \"namespace\", \"prefix\", \"label\", keySize)\n\n\tif kr.mostRecentKey != nil {\n\t\tt.Fatal(\"this test assumes a new key registry has no keys\")\n\t}\n\n\tkey1, cert1, err := generatePrivateKeyAndCert(keySize, validFor, cn)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt1 := time.Now()\n\n\tkey2, cert2, err := generatePrivateKeyAndCert(keySize, validFor, cn)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt2 := time.Now()\n\n\tif err := kr.registerNewKey(\"k2\", key2, cert2, t2); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif got, want := kr.mostRecentKey.private, key2; got != want {\n\t\tt.Errorf(\"got: %v, want: %v\", got, want)\n\t}\n\n\t// key1 is older, so it shouldn't replace key2 as the mostRecentKey\n\tif err := kr.registerNewKey(\"k1\", key1, cert1, t1); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif got, want := kr.mostRecentKey.private, key2; got != want {\n\t\tt.Errorf(\"got: %v, want: %v\", got, want)\n\t}\n}\n"
  },
  {
    "path": "pkg/controller/keys.go",
    "content": "package controller\n\nimport (\n\t\"context\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"errors\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/crypto\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n\tcertUtil \"k8s.io/client-go/util/cert\"\n\t\"k8s.io/client-go/util/keyutil\"\n)\n\n// SealedSecretsKeyLabel is that label used to locate active key pairs used to decrypt sealed secrets.\nconst SealedSecretsKeyLabel = \"sealedsecrets.bitnami.com/sealed-secrets-key\"\n\nvar (\n\t// ErrPrivateKeyNotRSA is returned when the private key is not a valid RSA key.\n\tErrPrivateKeyNotRSA = errors.New(\"private key is not an RSA key\")\n)\n\nfunc generatePrivateKeyAndCert(keySize int, validFor time.Duration, cn string) (*rsa.PrivateKey, *x509.Certificate, error) {\n\treturn crypto.GeneratePrivateKeyAndCert(keySize, validFor, cn)\n}\n\nfunc readKey(secret *v1.Secret) (*rsa.PrivateKey, []*x509.Certificate, error) {\n\tkey, err := keyutil.ParsePrivateKeyPEM(secret.Data[v1.TLSPrivateKeyKey])\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tswitch rsaKey := key.(type) {\n\tcase *rsa.PrivateKey:\n\t\tcerts, err := certUtil.ParseCertsPEM(secret.Data[v1.TLSCertKey])\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\treturn rsaKey, certs, nil\n\tdefault:\n\t\treturn nil, nil, ErrPrivateKeyNotRSA\n\t}\n}\n\ntype writeKeyOpt func(*writeKeyOpts)\ntype writeKeyOpts struct{ creationTime metav1.Time }\n\nfunc writeKeyWithCreationTime(t metav1.Time) writeKeyOpt {\n\treturn func(opts *writeKeyOpts) { opts.creationTime = t }\n}\n\nfunc writeKey(ctx context.Context, client kubernetes.Interface, key *rsa.PrivateKey, certs []*x509.Certificate, namespace, krLabel, prefix string, additionalAnnotations string, additionalLabels string, optSetters ...writeKeyOpt) (string, error) {\n\tvar opts writeKeyOpts\n\tfor _, o := range optSetters {\n\t\to(&opts)\n\t}\n\n\tcertbytes := []byte{}\n\tfor _, cert := range certs {\n\t\tcertbytes = append(certbytes, pem.EncodeToMemory(&pem.Block{Type: certUtil.CertificateBlockType, Bytes: cert.Raw})...)\n\t}\n\n\tlabels := map[string]string{\n\t\tkrLabel: \"active\",\n\t}\n\n\tannotations := map[string]string{}\n\n\tif additionalLabels != \"\" {\n\t\tfor _, label := range removeDuplicates(strings.Split(additionalLabels, \",\")) {\n\t\t\tkey := strings.Split(label, \"=\")[0]\n\t\t\tvalue := strings.Split(label, \"=\")[1]\n\t\t\tif key != krLabel {\n\t\t\t\tlabels[key] = value\n\t\t\t}\n\t\t}\n\t}\n\n\tif additionalAnnotations != \"\" {\n\t\tfor _, label := range removeDuplicates(strings.Split(additionalAnnotations, \",\")) {\n\t\t\tkey := strings.Split(label, \"=\")[0]\n\t\t\tvalue := strings.Split(label, \"=\")[1]\n\t\t\tannotations[key] = value\n\t\t}\n\t}\n\n\tsecret := v1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tNamespace:         namespace,\n\t\t\tGenerateName:      prefix,\n\t\t\tLabels:            labels,\n\t\t\tAnnotations:       annotations,\n\t\t\tCreationTimestamp: opts.creationTime,\n\t\t},\n\t\tData: map[string][]byte{\n\t\t\tv1.TLSPrivateKeyKey: pem.EncodeToMemory(&pem.Block{Type: keyutil.RSAPrivateKeyBlockType, Bytes: x509.MarshalPKCS1PrivateKey(key)}),\n\t\t\tv1.TLSCertKey:       certbytes,\n\t\t},\n\t\tType: v1.SecretTypeTLS,\n\t}\n\n\tcreatedSecret, err := client.CoreV1().Secrets(namespace).Create(ctx, &secret, metav1.CreateOptions{})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn createdSecret.Name, nil\n}\n"
  },
  {
    "path": "pkg/controller/keys_test.go",
    "content": "package controller\n\nimport (\n\t\"context\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"io\"\n\tmathrand \"math/rand\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/crypto\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n\tktesting \"k8s.io/client-go/testing\"\n\tcertUtil \"k8s.io/client-go/util/cert\"\n\t\"k8s.io/client-go/util/keyutil\"\n)\n\n// This is omg-not safe for real crypto use!\nfunc testRand() io.Reader {\n\treturn mathrand.New(mathrand.NewSource(42))\n}\n\nfunc signKey(r io.Reader, key *rsa.PrivateKey) (*x509.Certificate, error) {\n\treturn crypto.SignKey(r, key, time.Hour, \"testcn\")\n}\n\nfunc signKeyWithNotBefore(r io.Reader, key *rsa.PrivateKey, notBefore time.Time) (*x509.Certificate, error) {\n\treturn crypto.SignKeyWithNotBefore(r, key, notBefore, time.Hour, \"testcn\")\n}\n\nfunc TestReadKey(t *testing.T) {\n\trand := testRand()\n\n\tkey, err := rsa.GenerateKey(rand, 2048)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to generate test key: %v\", err)\n\t}\n\n\tcert, err := signKey(rand, key)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to self-sign key: %v\", err)\n\t}\n\n\tsecret := v1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"mykey\",\n\t\t\tNamespace: \"myns\",\n\t\t},\n\t\tData: map[string][]byte{\n\t\t\tv1.TLSPrivateKeyKey: pem.EncodeToMemory(&pem.Block{Type: keyutil.RSAPrivateKeyBlockType, Bytes: x509.MarshalPKCS1PrivateKey(key)}),\n\t\t\tv1.TLSCertKey:       pem.EncodeToMemory(&pem.Block{Type: certUtil.CertificateBlockType, Bytes: cert.Raw}),\n\t\t},\n\t\tType: v1.SecretTypeTLS,\n\t}\n\n\tkey2, cert2, err := readKey(&secret)\n\tif err != nil {\n\t\tt.Errorf(\"readKey() failed with: %v\", err)\n\t}\n\n\tif !reflect.DeepEqual(key, key2) {\n\t\tt.Errorf(\"Extracted key != original key\")\n\t}\n\n\tif !reflect.DeepEqual(cert, cert2[0]) {\n\t\tt.Errorf(\"Extracted cert != original cert\")\n\t}\n}\n\nfunc TestWriteKey(t *testing.T) {\n\tctx := context.Background()\n\trand := testRand()\n\tkey, err := rsa.GenerateKey(rand, 2048)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to generate test key: %v\", err)\n\t}\n\n\tcert, err := signKey(rand, key)\n\tif err != nil {\n\t\tt.Fatalf(\"signKey failed: %v\", err)\n\t}\n\n\tclient := fake.NewClientset()\n\n\tnamespace := \"myns\"\n\tdefaultLabel := \"default-label\"\n\tmyKey := \"mykey\"\n\tadditionalAnnotations := \"testAnnotation1=additional.annotation,test.annotation.2=test/2\"\n\tadditionalLabels := \"testLabel1=additional.label,test.label.2=test/2\"\n\t_, err = writeKey(ctx, client, key, []*x509.Certificate{cert}, namespace, defaultLabel, myKey, additionalAnnotations, additionalLabels)\n\tif err != nil {\n\t\tt.Errorf(\"writeKey() failed with: %v\", err)\n\t}\n\n\tt.Logf(\"actions: %v\", client.Actions())\n\n\tif a := findAction(client, \"create\", \"secrets\"); a == nil {\n\t\tt.Errorf(\"writeKey didn't create a secret\")\n\t} else if a.GetNamespace() != namespace {\n\t\tt.Errorf(\"writeKey() created key in wrong namespace!\")\n\t}\n\ta := findAction(client, \"create\", \"secrets\").(ktesting.CreateActionImpl)\n\tsecret, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(a.Object)\n\tgenerateName := secret[\"metadata\"].(map[string]interface{})[\"generateName\"].(string)\n\n\tif generateName != myKey {\n\t\tt.Errorf(\"writeKey didn't set the correct name\")\n\t}\n\n\tlabels := secret[\"metadata\"].(map[string]interface{})[\"labels\"]\n\tannotations := secret[\"metadata\"].(map[string]interface{})[\"annotations\"]\n\n\tif labels.(map[string]interface{})[defaultLabel] != \"active\" {\n\t\tt.Errorf(\"writeKey didn't set default label\")\n\t}\n\n\tfor _, label := range strings.Split(additionalLabels, \",\") {\n\t\tlabelKey := strings.Split(label, \"=\")[0]\n\t\tlabelValue := strings.Split(label, \"=\")[1]\n\t\tif labels.(map[string]interface{})[labelKey] != labelValue {\n\t\t\tt.Errorf(\"writeKey didn't set label %v to value '%v'\", labelKey, labelValue)\n\t\t}\n\t}\n\n\tfor _, annotation := range strings.Split(additionalAnnotations, \",\") {\n\t\tannotationKey := strings.Split(annotation, \"=\")[0]\n\t\tannotationValue := strings.Split(annotation, \"=\")[1]\n\t\tif annotations.(map[string]interface{})[annotationKey] != annotationValue {\n\t\t\tt.Errorf(\"writeKey didn't set annotation '%v' to value '%v'\", annotationKey, annotationValue)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/controller/main.go",
    "content": "package controller\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"crypto/x509\"\n\t\"io\"\n\t\"log/slog\"\n\t\"os\"\n\t\"os/signal\"\n\t\"sort\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/fields\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/rest\"\n\n\t\"k8s.io/client-go/informers\"\n\n\tssv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/apis/sealedsecrets/v1alpha1\"\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned\"\n\tsealedsecrets \"github.com/bitnami-labs/sealed-secrets/pkg/client/clientset/versioned\"\n\tssinformers \"github.com/bitnami-labs/sealed-secrets/pkg/client/informers/externalversions\"\n)\n\nvar (\n\t// Selector used to find existing public/private key pairs on startup.\n\tkeySelector = fields.OneTermEqualSelector(SealedSecretsKeyLabel, \"active\")\n)\n\n// Flags to configure the controller.\ntype Flags struct {\n\tKeyPrefix             string\n\tKeySize               int\n\tValidFor              time.Duration\n\tMyCN                  string\n\tKeyRenewPeriod        time.Duration\n\tKeyOrderPriority      string\n\tAcceptV1Data          bool\n\tKeyCutoffTime         string\n\tNamespaceAll          bool\n\tAdditionalNamespaces  string\n\tLabelSelector         string\n\tRateLimitPerSecond    int\n\tRateLimitBurst        int\n\tOldGCBehavior         bool\n\tUpdateStatus          bool\n\tSkipRecreate          bool\n\tLogInfoToStdout       bool\n\tLogLevel              string\n\tLogFormat             string\n\tPrivateKeyAnnotations string\n\tPrivateKeyLabels      string\n\tMaxRetries            int\n\tWatchForSecrets       bool\n\tKubeClientQPS         float32\n\tKubeClientBurst       int\n}\n\nfunc initKeyPrefix(keyPrefix string) (string, error) {\n\treturn validateKeyPrefix(keyPrefix)\n}\n\nfunc initKeyRegistry(ctx context.Context, client kubernetes.Interface, r io.Reader, namespace, prefix, label string, keysize int, keyOrderPriority string) (*KeyRegistry, error) {\n\tslog.Info(\"Searching for existing private keys\")\n\tsecretList, err := client.CoreV1().Secrets(namespace).List(ctx, metav1.ListOptions{\n\t\tLabelSelector: keySelector.String(),\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\titems := secretList.Items\n\n\ts, err := client.CoreV1().Secrets(namespace).Get(ctx, prefix, metav1.GetOptions{})\n\tif !errors.IsNotFound(err) {\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\titems = append(items, *s)\n\t\t// TODO(mkm): add the label to the legacy secret to simplify discovery and backups.\n\t}\n\n\tkeyRegistry := NewKeyRegistry(client, namespace, prefix, label, keysize)\n\tsort.Sort(ssv1alpha1.ByCreationTimestamp(items))\n\tfor _, secret := range items {\n\t\terr = registryNewKeyWithSecret(&secret, keyRegistry, keyOrderPriority)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn keyRegistry, nil\n}\n\nfunc registryNewKeyWithSecret(secret *v1.Secret, keyRegistry *KeyRegistry, keyOrderPriority string) error {\n\tkey, certs, err := readKey(secret)\n\tif err != nil {\n\t\tslog.Error(\"Error reading key\", \"secret\", secret.Name, \"error\", err)\n\t}\n\n\t// Select ordering time based on the keyOrderPriority flag\n\torderingTime := getKeyOrderPriority(keyOrderPriority, certs[0], secret)\n\n\tif err := keyRegistry.registerNewKey(secret.Name, key, certs[0], orderingTime); err != nil {\n\t\treturn err\n\t}\n\tslog.Info(\"registered private key\", \"secretname\", secret.Name)\n\treturn nil\n}\n\nfunc getKeyOrderPriority(keyOrderPriority string, cert *x509.Certificate, secret *v1.Secret) time.Time {\n\tswitch keyOrderPriority {\n\tcase \"CertNotBefore\":\n\t\treturn cert.NotBefore\n\tcase \"SecretCreationTimestamp\":\n\t\treturn secret.GetCreationTimestamp().Time\n\tdefault:\n\t\tslog.Error(\"Invalid keyOrderPriority. Use CertNotBefore or SecretCreationTimestamp\", \"keyOrderPriority\", keyOrderPriority)\n\t}\n\treturn cert.NotBefore\n}\n\nfunc myNamespace() string {\n\tif ns := os.Getenv(\"POD_NAMESPACE\"); ns != \"\" {\n\t\treturn ns\n\t}\n\n\t// Fall back to the namespace associated with the service account token, if available\n\tif data, err := os.ReadFile(\"/var/run/secrets/kubernetes.io/serviceaccount/namespace\"); err == nil {\n\t\tif ns := strings.TrimSpace(string(data)); len(ns) > 0 {\n\t\t\treturn ns\n\t\t}\n\t}\n\n\treturn metav1.NamespaceDefault\n}\n\n// Initialises the first key and starts the rotation job. returns an early trigger function.\n// A period of 0 deactivates automatic rotation, but manual rotation (e.g. triggered by SIGUSR1)\n// is still honoured.\nfunc initKeyRenewal(ctx context.Context, registry *KeyRegistry, period, validFor time.Duration, cutoffTime time.Time, cn string, privateKeyAnnotations string, privateKeyLabels string) (func(), error) {\n\t// Create a new key if it's the first key,\n\t// or if it's older than cutoff time.\n\tif len(registry.keys) == 0 || registry.mostRecentKey.orderingTime.Before(cutoffTime) {\n\t\tif _, err := registry.generateKey(ctx, validFor, cn, privateKeyAnnotations, privateKeyLabels); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// wrapper function to log error thrown by generateKey function\n\tkeyGenFunc := func() {\n\t\tif _, err := registry.generateKey(ctx, validFor, cn, privateKeyAnnotations, privateKeyLabels); err != nil {\n\t\t\tslog.Error(\"Failed to generate new key\", \"error\", err)\n\t\t}\n\t}\n\tif period == 0 {\n\t\treturn keyGenFunc, nil\n\t}\n\n\t// If key rotation is enabled, we'll rotate the key when the most recent\n\t// key becomes stale (older than period).\n\tmostRecentKeyAge := time.Since(registry.mostRecentKey.orderingTime)\n\tinitialDelay := period - mostRecentKeyAge\n\tif initialDelay < 0 {\n\t\tinitialDelay = 0\n\t}\n\treturn ScheduleJobWithTrigger(initialDelay, period, keyGenFunc), nil\n}\n\nfunc Main(f *Flags, version string) error {\n\tregisterMetrics(version)\n\n\tconfig, err := rest.InClusterConfig()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tconfig.QPS = f.KubeClientQPS\n\tconfig.Burst = f.KubeClientBurst\n\n\tclientset, err := kubernetes.NewForConfig(config)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tssclientset, err := sealedsecrets.NewForConfig(config)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tmyNs := myNamespace()\n\tctx := context.Background()\n\n\tprefix, err := initKeyPrefix(f.KeyPrefix)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tkeyRegistry, err := initKeyRegistry(ctx, clientset, rand.Reader, myNs, prefix, SealedSecretsKeyLabel, f.KeySize, f.KeyOrderPriority)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar ct time.Time\n\tif f.KeyCutoffTime != \"\" {\n\t\tvar err error\n\t\tct, err = time.Parse(time.RFC1123Z, f.KeyCutoffTime)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\ttrigger, err := initKeyRenewal(ctx, keyRegistry, f.KeyRenewPeriod, f.ValidFor, ct, f.MyCN, f.PrivateKeyAnnotations, f.PrivateKeyLabels)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tinitKeyGenSignalListener(trigger)\n\n\tnamespace := v1.NamespaceAll\n\tif !f.NamespaceAll || f.AdditionalNamespaces != \"\" {\n\t\tnamespace = myNamespace()\n\t\tslog.Info(\"Starting informer\", \"namespace\", namespace)\n\t}\n\n\tvar tweakopts func(*metav1.ListOptions) = nil\n\tif f.LabelSelector != \"\" {\n\t\ttweakopts = func(options *metav1.ListOptions) {\n\t\t\toptions.LabelSelector = f.LabelSelector\n\t\t}\n\t}\n\n\tcontroller, err := prepareController(clientset, namespace, myNs, tweakopts, f, ssclientset, keyRegistry)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcontroller.oldGCBehavior = f.OldGCBehavior\n\tcontroller.updateStatus = f.UpdateStatus\n\n\tstop := make(chan struct{})\n\tdefer close(stop)\n\n\tgo controller.Run(stop)\n\n\tif f.AdditionalNamespaces != \"\" {\n\t\taddNS := removeDuplicates(strings.Split(f.AdditionalNamespaces, \",\"))\n\n\t\tfor _, ns := range addNS {\n\t\t\tif _, err := clientset.CoreV1().Namespaces().Get(ctx, ns, metav1.GetOptions{}); err != nil {\n\t\t\t\tif errors.IsNotFound(err) {\n\t\t\t\t\tslog.Error(\"namespace doesn't exist\", \"namespace\", ns)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ns != namespace {\n\t\t\t\tctlr, err := prepareController(clientset, ns, myNs, tweakopts, f, ssclientset, keyRegistry)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tctlr.oldGCBehavior = f.OldGCBehavior\n\t\t\t\tctlr.updateStatus = f.UpdateStatus\n\t\t\t\tslog.Info(\"Starting informer\", \"namespace\", ns)\n\t\t\t\tgo ctlr.Run(stop)\n\t\t\t}\n\t\t}\n\t}\n\n\tcp := func() ([]*x509.Certificate, error) {\n\t\tcert, err := keyRegistry.getCert()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn []*x509.Certificate{cert}, nil\n\t}\n\n\tserver := httpserver(cp, controller.AttemptUnseal, controller.Rotate, f.RateLimitBurst, f.RateLimitPerSecond)\n\tserverMetrics := httpserverMetrics()\n\n\tsigterm := make(chan os.Signal, 1)\n\tsignal.Notify(sigterm, syscall.SIGTERM)\n\t<-sigterm\n\n\tif err := server.Shutdown(context.Background()); err != nil {\n\t\treturn err\n\t}\n\n\tif err := serverMetrics.Shutdown(context.Background()); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc prepareController(\n\tclientset kubernetes.Interface,\n\tnamespace string,\n\tkeyNamespace string,\n\ttweakopts func(*metav1.ListOptions),\n\tf *Flags,\n\tssclientset versioned.Interface,\n\tkeyRegistry *KeyRegistry,\n) (*Controller, error) {\n\tkinformer := initSecretInformerFactory(clientset, keyNamespace, func(options *metav1.ListOptions) {\n\t\toptions.LabelSelector = keySelector.String()\n\t}, f.WatchForSecrets)\n\tsinformer := initSecretInformerFactory(clientset, namespace, tweakopts, !f.SkipRecreate)\n\tssinformer := ssinformers.NewFilteredSharedInformerFactory(ssclientset, 0, namespace, tweakopts)\n\tcontroller, err := NewController(clientset, ssclientset, ssinformer, sinformer, kinformer, keyRegistry, f.MaxRetries, f.KeyOrderPriority)\n\treturn controller, err\n}\n\nfunc initSecretInformerFactory(clientset kubernetes.Interface, ns string, tweakopts func(*metav1.ListOptions), enabled bool) informers.SharedInformerFactory {\n\tif !enabled {\n\t\treturn nil\n\t}\n\treturn informers.NewSharedInformerFactoryWithOptions(clientset, 0, informers.WithNamespace(ns), informers.WithTweakListOptions(tweakopts))\n}\n"
  },
  {
    "path": "pkg/controller/main_test.go",
    "content": "package controller\n\nimport (\n\t\"context\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tkrand \"k8s.io/apimachinery/pkg/util/rand\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n\tktesting \"k8s.io/client-go/testing\"\n\tcertUtil \"k8s.io/client-go/util/cert\"\n\t\"k8s.io/client-go/util/keyutil\"\n)\n\nfunc findAction(fake *fake.Clientset, verb, resource string) ktesting.Action {\n\tfor _, a := range fake.Actions() {\n\t\tif a.Matches(verb, resource) {\n\t\t\treturn a\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc hasAction(fake *fake.Clientset, verb, resource string) bool {\n\treturn findAction(fake, verb, resource) != nil\n}\n\n// generateNameReactor implements the logic required for the GenerateName field to work when using\n// the fake client. Add it with client.PrependReactor to your fake client.\nfunc generateNameReactor(action ktesting.Action) (handled bool, ret runtime.Object, err error) {\n\ts := action.(ktesting.CreateAction).GetObject().(*v1.Secret)\n\tif s.Name == \"\" && s.GenerateName != \"\" {\n\t\ts.Name = fmt.Sprintf(\"%s-%s\", s.GenerateName, krand.String(16))\n\t}\n\treturn false, nil, nil\n}\n\nfunc TestInitKeyRegistry(t *testing.T) {\n\tctx := context.Background()\n\trand := testRand()\n\tclient := fake.NewClientset()\n\tclient.PrependReactor(\"create\", \"secrets\", generateNameReactor)\n\n\tregistry, err := initKeyRegistry(ctx, client, rand, \"namespace\", \"prefix\", \"label\", 1024, \"CertNotBefore\")\n\tif err != nil {\n\t\tt.Fatalf(\"initKeyRegistry() returned err: %v\", err)\n\t}\n\n\t// Add a key to the controller for second test\n\tvalidFor := time.Hour\n\tcn := \"my-cn\"\n\t_, err = registry.generateKey(ctx, validFor, cn, \"\", \"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif !hasAction(client, \"create\", \"secrets\") {\n\t\tt.Fatalf(\"Error adding initial key to registry\")\n\t}\n\tclient.ClearActions()\n\n\t// Due to limitations of the fake client, we cannot test whether initKeyRegistry is able\n\t// to pick up existing keys\n\t_, err = initKeyRegistry(ctx, client, rand, \"namespace\", \"prefix\", \"label\", 1024, \"CertNotBefore\")\n\tif err != nil {\n\t\tt.Fatalf(\"initKeyRegistry() returned err: %v\", err)\n\t}\n\tif !hasAction(client, \"list\", \"secrets\") {\n\t\tt.Errorf(\"initKeyRegistry() failed to read existing keys\")\n\t}\n}\n\nfunc TestInitKeyRotation(t *testing.T) {\n\tctx := context.Background()\n\trand := testRand()\n\tclient := fake.NewClientset()\n\tclient.PrependReactor(\"create\", \"secrets\", generateNameReactor)\n\n\tregistry, err := initKeyRegistry(ctx, client, rand, \"namespace\", \"prefix\", \"label\", 1024, \"CertNotBefore\")\n\tif err != nil {\n\t\tt.Fatalf(\"initKeyRegistry() returned err: %v\", err)\n\t}\n\n\tvalidFor := time.Hour\n\tcn := \"my-cn\"\n\tkeyGenTrigger, err := initKeyRenewal(ctx, registry, 0, validFor, time.Time{}, cn, \"\", \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"initKeyRenewal() returned err: %v\", err)\n\t}\n\tif !hasAction(client, \"create\", \"secrets\") {\n\t\tt.Errorf(\"initKeyRenewal() failed to generate an initial key\")\n\t}\n\n\tclient.ClearActions()\n\n\t// Test the trigger function\n\t// Activates trigger and polls client every 50 ms up to 10s for the appropriate action\n\tkeyGenTrigger()\n\tmaxWait := 10 * time.Second\n\tendTime := time.Now().Add(maxWait)\n\tsuccessful := false\n\tfor time.Now().Before(endTime) {\n\t\ttime.Sleep(50 * time.Millisecond)\n\t\tif hasAction(client, \"create\", \"secrets\") {\n\t\t\tsuccessful = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !successful {\n\t\tt.Errorf(\"trigger function failed to activate early key generation\")\n\t}\n}\n\nfunc TestInitKeyRotationTick(t *testing.T) {\n\tctx := context.Background()\n\trand := testRand()\n\tclient := fake.NewClientset()\n\tclient.PrependReactor(\"create\", \"secrets\", generateNameReactor)\n\n\tregistry, err := initKeyRegistry(ctx, client, rand, \"namespace\", \"prefix\", \"label\", 1024, \"CertNotBefore\")\n\tif err != nil {\n\t\tt.Fatalf(\"initKeyRegistry() returned err: %v\", err)\n\t}\n\n\tvalidFor := time.Hour\n\tcn := \"my-cn\"\n\t_, err = initKeyRenewal(ctx, registry, 100*time.Millisecond, validFor, time.Time{}, cn, \"\", \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"initKeyRenewal() returned err: %v\", err)\n\t}\n\tif !hasAction(client, \"create\", \"secrets\") {\n\t\tt.Errorf(\"initKeyRenewal() failed to generate an initial key\")\n\t}\n\n\tclient.ClearActions()\n\n\tmaxWait := 10 * time.Second\n\tendTime := time.Now().Add(maxWait)\n\tsuccessful := false\n\tfor time.Now().Before(endTime) {\n\t\ttime.Sleep(50 * time.Millisecond)\n\t\tif hasAction(client, \"create\", \"secrets\") {\n\t\t\tsuccessful = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !successful {\n\t\tt.Errorf(\"trigger function failed to activate early key generation\")\n\t}\n}\n\nfunc TestReuseKey(t *testing.T) {\n\tctx := context.Background()\n\trand := testRand()\n\tkey, err := rsa.GenerateKey(rand, 2048)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to generate test key: %v\", err)\n\t}\n\n\tcert, err := signKey(rand, key)\n\tif err != nil {\n\t\tt.Fatalf(\"signKey failed: %v\", err)\n\t}\n\n\tclient := fake.NewClientset()\n\tclient.PrependReactor(\"create\", \"secrets\", generateNameReactor)\n\n\t_, err = writeKey(ctx, client, key, []*x509.Certificate{cert}, \"namespace\", SealedSecretsKeyLabel, \"prefix\", \"\", \"\")\n\tif err != nil {\n\t\tt.Errorf(\"writeKey() failed with: %v\", err)\n\t}\n\n\tclient.ClearActions()\n\n\tregistry, err := initKeyRegistry(ctx, client, rand, \"namespace\", \"prefix\", SealedSecretsKeyLabel, 1024, \"CertNotBefore\")\n\tif err != nil {\n\t\tt.Fatalf(\"initKeyRegistry() returned err: %v\", err)\n\t}\n\n\tvalidFor := time.Hour\n\tcn := \"my-cn\"\n\t_, err = initKeyRenewal(ctx, registry, 0, validFor, time.Time{}, cn, \"\", \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"initKeyRenewal() returned err: %v\", err)\n\t}\n\tif hasAction(client, \"create\", \"secrets\") {\n\t\tt.Errorf(\"initKeyRenewal() should not create a new secret when one already exist and rotation is deactivated\")\n\t}\n}\n\nfunc TestRenewStaleKey(t *testing.T) {\n\tctx := context.Background()\n\trand := testRand()\n\tkey, err := rsa.GenerateKey(rand, 2048)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to generate test key: %v\", err)\n\t}\n\n\t// we'll simulate the existence of a secret that is about to expire\n\t// by making it old enough so that it's just \"staleness\" short of using\n\t// the full rotation \"period\".\n\tconst (\n\t\tperiod    = 20 * time.Second\n\t\tstaleness = 100 * time.Millisecond\n\t\toldAge    = period - staleness\n\t)\n\tnotBefore := time.Now().Add(-oldAge)\n\n\tcert, err := signKeyWithNotBefore(rand, key, notBefore)\n\tif err != nil {\n\t\tt.Fatalf(\"signKey failed: %v\", err)\n\t}\n\n\tclient := fake.NewClientset()\n\tclient.PrependReactor(\"create\", \"secrets\", generateNameReactor)\n\n\t_, err = writeKey(ctx, client, key, []*x509.Certificate{cert}, \"namespace\", SealedSecretsKeyLabel, \"prefix\", \"\", \"\")\n\tif err != nil {\n\t\tt.Errorf(\"writeKey() failed with: %v\", err)\n\t}\n\n\tregistry, err := initKeyRegistry(ctx, client, rand, \"namespace\", \"prefix\", SealedSecretsKeyLabel, 1024, \"CertNotBefore\")\n\tif err != nil {\n\t\tt.Fatalf(\"initKeyRegistry() returned err: %v\", err)\n\t}\n\n\tvalidFor := time.Hour\n\tcn := \"my-cn\"\n\t_, err = initKeyRenewal(ctx, registry, period, validFor, time.Time{}, cn, \"\", \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"initKeyRenewal() returned err: %v\", err)\n\t}\n\n\tclient.ClearActions()\n\n\tmaxWait := 1 * time.Second\n\tendTime := time.Now().Add(maxWait)\n\tsuccessful := false\n\tfor time.Now().Before(endTime) {\n\t\ttime.Sleep(50 * time.Millisecond)\n\t\tif hasAction(client, \"create\", \"secrets\") {\n\t\t\tsuccessful = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !successful {\n\t\tt.Errorf(\"trigger function failed to activate early key generation\")\n\t}\n}\n\nfunc TestKeyCutoff(t *testing.T) {\n\tctx := context.Background()\n\trand := testRand()\n\tkey, err := rsa.GenerateKey(rand, 2048)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to generate test key: %v\", err)\n\t}\n\n\tcert, err := signKey(rand, key)\n\tif err != nil {\n\t\tt.Fatalf(\"signKey failed: %v\", err)\n\t}\n\n\t// we'll simulate the existence of a secret that would be still valid\n\t// according to our rotation period, if it were not for it being older than the cutoff date.\n\tconst (\n\t\tperiod = 24 * time.Hour\n\t\toldAge = 1 * time.Hour\n\t)\n\tclient := fake.NewClientset()\n\tclient.PrependReactor(\"create\", \"secrets\", generateNameReactor)\n\n\t_, err = writeKey(ctx, client, key, []*x509.Certificate{cert}, \"namespace\", SealedSecretsKeyLabel, \"prefix\", \"\", \"\",\n\t\twriteKeyWithCreationTime(metav1.NewTime(time.Now().Add(-oldAge))))\n\tif err != nil {\n\t\tt.Errorf(\"writeKey() failed with: %v\", err)\n\t}\n\n\tregistry, err := initKeyRegistry(ctx, client, rand, \"namespace\", \"prefix\", SealedSecretsKeyLabel, 1024, \"CertNotBefore\")\n\tif err != nil {\n\t\tt.Fatalf(\"initKeyRegistry() returned err: %v\", err)\n\t}\n\n\tclient.ClearActions()\n\n\t// by setting cutoff to \"now\" we effectively force the creation of a new key.\n\tvalidFor := time.Hour\n\tcn := \"my-cn\"\n\t_, err = initKeyRenewal(ctx, registry, period, validFor, time.Now(), cn, \"\", \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"initKeyRenewal() returned err: %v\", err)\n\t}\n\n\tif !hasAction(client, \"create\", \"secrets\") {\n\t\tt.Errorf(\"trigger function failed to activate early key generation\")\n\t}\n}\n\nfunc writeLegacyKey(ctx context.Context, client kubernetes.Interface, key *rsa.PrivateKey, certs []*x509.Certificate, namespace, name string) (string, error) {\n\tcertbytes := []byte{}\n\tfor _, cert := range certs {\n\t\tcertbytes = append(certbytes, pem.EncodeToMemory(&pem.Block{Type: certUtil.CertificateBlockType, Bytes: cert.Raw})...)\n\t}\n\tsecret := v1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tNamespace: namespace,\n\t\t\tName:      name,\n\t\t},\n\t\tData: map[string][]byte{\n\t\t\tv1.TLSPrivateKeyKey: pem.EncodeToMemory(&pem.Block{Type: keyutil.RSAPrivateKeyBlockType, Bytes: x509.MarshalPKCS1PrivateKey(key)}),\n\t\t\tv1.TLSCertKey:       certbytes,\n\t\t},\n\t\tType: v1.SecretTypeTLS,\n\t}\n\n\tcreatedSecret, err := client.CoreV1().Secrets(namespace).Create(ctx, &secret, metav1.CreateOptions{})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn createdSecret.Name, nil\n}\n\nfunc TestLegacySecret(t *testing.T) {\n\tctx := context.Background()\n\trand := testRand()\n\tkey, err := rsa.GenerateKey(rand, 2048)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to generate test key: %v\", err)\n\t}\n\n\tcert, err := signKey(rand, key)\n\tif err != nil {\n\t\tt.Fatalf(\"signKey failed: %v\", err)\n\t}\n\n\tclient := fake.NewClientset()\n\tclient.PrependReactor(\"create\", \"secrets\", generateNameReactor)\n\n\t_, err = writeLegacyKey(ctx, client, key, []*x509.Certificate{cert}, \"namespace\", \"prefix\")\n\tif err != nil {\n\t\tt.Errorf(\"writeKey() failed with: %v\", err)\n\t}\n\n\tclient.ClearActions()\n\n\tregistry, err := initKeyRegistry(ctx, client, rand, \"namespace\", \"prefix\", SealedSecretsKeyLabel, 1024, \"CertNotBefore\")\n\tif err != nil {\n\t\tt.Fatalf(\"initKeyRegistry() returned err: %v\", err)\n\t}\n\n\tvalidFor := time.Hour\n\tcn := \"my-cn\"\n\t_, err = initKeyRenewal(ctx, registry, 0, validFor, time.Time{}, cn, \"\", \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"initKeyRenewal() returned err: %v\", err)\n\t}\n\tif hasAction(client, \"create\", \"secrets\") {\n\t\tt.Errorf(\"initKeyRenewal() should not create a new secret when one already exist and rotation is deactivated\")\n\t}\n}\n"
  },
  {
    "path": "pkg/controller/metrics.go",
    "content": "package controller\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/apis/sealedsecrets/v1alpha1\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/collectors\"\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n\tv1 \"k8s.io/api/core/v1\"\n)\n\n// Define Prometheus Exporter namespace (prefix) for all metric names.\nconst metricNamespace string = \"sealed_secrets_controller\"\n\nconst (\n\tlabelNamespace = \"namespace\"\n\tlabelName      = \"name\"\n\tlabelCondition = \"condition\"\n\tlabelInstance  = \"ss_app_kubernetes_io_instance\"\n)\n\nvar conditionStatusToGaugeValue = map[v1.ConditionStatus]float64{\n\tv1.ConditionFalse:   -1,\n\tv1.ConditionUnknown: 0,\n\tv1.ConditionTrue:    1,\n}\n\n// Define Prometheus metrics to expose.\nvar (\n\tbuildInfo prometheus.Gauge\n\t// TODO: rename metric, change increment logic, or accept behaviour\n\t// when a SealedSecret is deleted the unseal() function is called which is\n\t// not technically an 'unseal request'.\n\tunsealRequestsTotal = prometheus.NewCounter(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"unseal_requests_total\",\n\t\t\tHelp:      \"Total number of sealed secret unseal requests\",\n\t\t},\n\t)\n\tunsealErrorsTotal = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"unseal_errors_total\",\n\t\t\tHelp:      \"Total number of sealed secret unseal errors by reason\",\n\t\t},\n\t\t[]string{\"reason\", \"namespace\"},\n\t)\n\n\tconditionInfo = prometheus.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"condition_info\",\n\t\t\tHelp:      \"Current SealedSecret condition status. Values are -1 (false), 0 (unknown or absent), 1 (true)\",\n\t\t},\n\t\t[]string{labelNamespace, labelName, labelCondition, labelInstance},\n\t)\n\n\thttpRequestsTotal = prometheus.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"http_requests_total\",\n\t\t\tHelp:      \"A counter for requests to the wrapped handler.\",\n\t\t},\n\t\t[]string{\"path\", \"code\", \"method\"},\n\t)\n\n\thttpRequestDurationSeconds = prometheus.NewHistogramVec(\n\t\tprometheus.HistogramOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"http_request_duration_seconds\",\n\t\t\tHelp:      \"A histogram of latencies for requests.\",\n\t\t\tBuckets:   prometheus.DefBuckets,\n\t\t},\n\t\t[]string{\"path\", \"method\"},\n\t)\n)\n\nfunc registerMetrics(version string) {\n\tbuildInfo = prometheus.NewGauge(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace:   metricNamespace,\n\t\t\tName:        \"build_info\",\n\t\t\tHelp:        \"Build information.\",\n\t\t\tConstLabels: prometheus.Labels{\"revision\": version},\n\t\t},\n\t)\n\t// Register metrics with Prometheus\n\tprometheus.MustRegister(buildInfo)\n\tprometheus.MustRegister(collectors.NewBuildInfoCollector())\n\tprometheus.MustRegister(unsealRequestsTotal)\n\tprometheus.MustRegister(unsealErrorsTotal)\n\tprometheus.MustRegister(conditionInfo)\n\tprometheus.MustRegister(httpRequestsTotal)\n\tprometheus.MustRegister(httpRequestDurationSeconds)\n}\n\n// ObserveCondition sets a `condition_info` Gauge according to a SealedSecret status.\nfunc ObserveCondition(ssecret *v1alpha1.SealedSecret) {\n\tif ssecret.Status == nil {\n\t\treturn\n\t}\n\tfor _, condition := range ssecret.Status.Conditions {\n\t\tconditionInfo.With(prometheus.Labels{\n\t\t\tlabelNamespace: ssecret.Namespace,\n\t\t\tlabelName:      ssecret.Name,\n\t\t\tlabelCondition: string(condition.Type),\n\t\t\tlabelInstance:  ssecret.Labels[\"app.kubernetes.io/instance\"],\n\t\t}).Set(conditionStatusToGaugeValue[condition.Status])\n\t}\n}\n\n// UnregisterCondition unregisters Gauges associated to a SealedSecret conditions.\nfunc UnregisterCondition(ssecret *v1alpha1.SealedSecret) {\n\tif ssecret.Status == nil {\n\t\treturn\n\t}\n\tfor _, condition := range ssecret.Status.Conditions {\n\t\tconditionInfo.DeleteLabelValues(ssecret.Namespace, ssecret.Name, string(condition.Type), ssecret.Labels[\"app.kubernetes.io/instance\"])\n\t}\n}\n\n// Instrument HTTP handler.\nfunc Instrument(path string, h http.Handler) http.Handler {\n\treturn promhttp.InstrumentHandlerDuration(httpRequestDurationSeconds.MustCurryWith(prometheus.Labels{\"path\": path}),\n\t\tpromhttp.InstrumentHandlerCounter(httpRequestsTotal.MustCurryWith(prometheus.Labels{\"path\": path}), h))\n}\n"
  },
  {
    "path": "pkg/controller/metrics_test.go",
    "content": "package controller\n\nimport (\n\t\"testing\"\n\n\tssv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/apis/sealedsecrets/v1alpha1\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\tdto \"github.com/prometheus/client_model/go\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// setupTestMetrics creates a fresh metrics setup for testing\nfunc setupTestMetrics() *prometheus.Registry {\n\tregistry := prometheus.NewRegistry()\n\n\t// Create a new conditionInfo metric for testing\n\ttestConditionInfo := prometheus.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: metricNamespace,\n\t\t\tName:      \"condition_info\",\n\t\t\tHelp:      \"Current SealedSecret condition status. Values are -1 (false), 0 (unknown or absent), 1 (true)\",\n\t\t},\n\t\t[]string{labelNamespace, labelName, labelCondition, labelInstance},\n\t)\n\n\tregistry.MustRegister(testConditionInfo)\n\n\t// Replace the global conditionInfo for testing\n\tconditionInfo = testConditionInfo\n\n\treturn registry\n}\n\nfunc TestObserveCondition(t *testing.T) {\n\tregistry := setupTestMetrics()\n\n\tssecret := &ssv1alpha1.SealedSecret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tNamespace: \"test-ns\",\n\t\t\tName:      \"test-secret\",\n\t\t\tLabels: map[string]string{\n\t\t\t\t\"app.kubernetes.io/instance\": \"test-instance\",\n\t\t\t},\n\t\t},\n\t\tStatus: &ssv1alpha1.SealedSecretStatus{\n\t\t\tConditions: []ssv1alpha1.SealedSecretCondition{\n\t\t\t\t{\n\t\t\t\t\tType:   ssv1alpha1.SealedSecretSynced,\n\t\t\t\t\tStatus: corev1.ConditionTrue,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tObserveCondition(ssecret)\n\n\t// Verify metric was created\n\tmetricFamilies, err := registry.Gather()\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to gather metrics: %v\", err)\n\t}\n\n\tfound := false\n\tfor _, mf := range metricFamilies {\n\t\tif mf.GetName() == \"sealed_secrets_controller_condition_info\" {\n\t\t\tfor _, metric := range mf.GetMetric() {\n\t\t\t\tlabels := metric.GetLabel()\n\t\t\t\tif getLabel(labels, \"namespace\") == \"test-ns\" &&\n\t\t\t\t\tgetLabel(labels, \"name\") == \"test-secret\" &&\n\t\t\t\t\tgetLabel(labels, \"condition\") == \"Synced\" &&\n\t\t\t\t\tgetLabel(labels, \"ss_app_kubernetes_io_instance\") == \"test-instance\" {\n\t\t\t\t\tfound = true\n\t\t\t\t\tif metric.GetGauge().GetValue() != 1.0 {\n\t\t\t\t\t\tt.Errorf(\"Expected metric value 1.0, got %f\", metric.GetGauge().GetValue())\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif !found {\n\t\tt.Error(\"Expected metric not found\")\n\t}\n}\n\nfunc TestUnregisterCondition(t *testing.T) {\n\tregistry := setupTestMetrics()\n\n\tssecret := &ssv1alpha1.SealedSecret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tNamespace: \"test-ns\",\n\t\t\tName:      \"test-secret\",\n\t\t\tLabels: map[string]string{\n\t\t\t\t\"app.kubernetes.io/instance\": \"test-instance\",\n\t\t\t},\n\t\t},\n\t\tStatus: &ssv1alpha1.SealedSecretStatus{\n\t\t\tConditions: []ssv1alpha1.SealedSecretCondition{\n\t\t\t\t{\n\t\t\t\t\tType:   ssv1alpha1.SealedSecretSynced,\n\t\t\t\t\tStatus: corev1.ConditionTrue,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\t// First observe the condition to create the metric\n\tObserveCondition(ssecret)\n\n\t// Verify metric exists\n\tmetricFamilies, err := registry.Gather()\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to gather metrics: %v\", err)\n\t}\n\n\tmetricExists := func() bool {\n\t\tfor _, mf := range metricFamilies {\n\t\t\tif mf.GetName() == \"sealed_secrets_controller_condition_info\" {\n\t\t\t\tfor _, metric := range mf.GetMetric() {\n\t\t\t\t\tlabels := metric.GetLabel()\n\t\t\t\t\tif getLabel(labels, \"namespace\") == \"test-ns\" &&\n\t\t\t\t\t\tgetLabel(labels, \"name\") == \"test-secret\" &&\n\t\t\t\t\t\tgetLabel(labels, \"condition\") == \"Synced\" &&\n\t\t\t\t\t\tgetLabel(labels, \"ss_app_kubernetes_io_instance\") == \"test-instance\" {\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n\n\tif !metricExists() {\n\t\tt.Fatal(\"Metric should exist before unregistering\")\n\t}\n\n\t// Now unregister the condition\n\tUnregisterCondition(ssecret)\n\n\t// Verify metric was removed\n\tmetricFamilies, err = registry.Gather()\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to gather metrics: %v\", err)\n\t}\n\n\tif metricExists() {\n\t\tt.Error(\"Metric should have been removed after unregistering\")\n\t}\n}\n\nfunc TestUnregisterConditionWithNilStatus(t *testing.T) {\n\tssecret := &ssv1alpha1.SealedSecret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tNamespace: \"test-ns\",\n\t\t\tName:      \"test-secret\",\n\t\t},\n\t\tStatus: nil,\n\t}\n\n\t// Should not panic\n\tUnregisterCondition(ssecret)\n}\n\nfunc TestObserveConditionWithNilStatus(t *testing.T) {\n\tssecret := &ssv1alpha1.SealedSecret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tNamespace: \"test-ns\",\n\t\t\tName:      \"test-secret\",\n\t\t},\n\t\tStatus: nil,\n\t}\n\n\t// Should not panic\n\tObserveCondition(ssecret)\n}\n\nfunc TestUnregisterConditionWithMissingLabel(t *testing.T) {\n\tregistry := setupTestMetrics()\n\n\tssecret := &ssv1alpha1.SealedSecret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tNamespace: \"test-ns\",\n\t\t\tName:      \"test-secret\",\n\t\t\t// Missing app.kubernetes.io/instance label\n\t\t},\n\t\tStatus: &ssv1alpha1.SealedSecretStatus{\n\t\t\tConditions: []ssv1alpha1.SealedSecretCondition{\n\t\t\t\t{\n\t\t\t\t\tType:   ssv1alpha1.SealedSecretSynced,\n\t\t\t\t\tStatus: corev1.ConditionTrue,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\t// First observe the condition to create the metric (with empty instance label)\n\tObserveCondition(ssecret)\n\n\t// Now unregister the condition - should work with empty instance label\n\tUnregisterCondition(ssecret)\n\n\t// Verify metric was removed\n\tmetricFamilies, err := registry.Gather()\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to gather metrics: %v\", err)\n\t}\n\n\tfor _, mf := range metricFamilies {\n\t\tif mf.GetName() == \"sealed_secrets_controller_condition_info\" {\n\t\t\tfor _, metric := range mf.GetMetric() {\n\t\t\t\tlabels := metric.GetLabel()\n\t\t\t\tif getLabel(labels, \"namespace\") == \"test-ns\" &&\n\t\t\t\t\tgetLabel(labels, \"name\") == \"test-secret\" &&\n\t\t\t\t\tgetLabel(labels, \"condition\") == \"Synced\" &&\n\t\t\t\t\tgetLabel(labels, \"ss_app_kubernetes_io_instance\") == \"\" {\n\t\t\t\t\tt.Error(\"Metric should have been removed after unregistering\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Helper function to get label value from metric labels\nfunc getLabel(labels []*dto.LabelPair, name string) string {\n\tfor _, label := range labels {\n\t\tif label.GetName() == name {\n\t\t\treturn label.GetValue()\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "pkg/controller/server.go",
    "content": "package controller\n\nimport (\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"io\"\n\t\"log\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n\n\tflag \"github.com/spf13/pflag\"\n\t\"github.com/throttled/throttled\"\n\t\"github.com/throttled/throttled/store/memstore\"\n\tcertUtil \"k8s.io/client-go/util/cert\"\n)\n\nvar (\n\tlistenAddr        = flag.String(\"listen-addr\", \":8080\", \"HTTP serving address.\")\n\tlistenMetricsAddr = flag.String(\"listen-metrics-addr\", \":8081\", \"HTTP metrics serving address.\")\n\treadTimeout       = flag.Duration(\"read-timeout\", 2*time.Minute, \"HTTP request timeout.\")\n\twriteTimeout      = flag.Duration(\"write-timeout\", 2*time.Minute, \"HTTP response timeout.\")\n)\n\n// Called on every request to /cert.  Errors will be logged and return a 500.\ntype certProvider func() ([]*x509.Certificate, error)\ntype secretChecker func([]byte) (bool, error)\ntype secretRotator func([]byte) ([]byte, error)\n\n// httpserver starts an HTTP that exposes core functionality like serving the public key\n// or secret rotation and validation. This endpoint is designed to be accessible by\n// all users of a given cluster. It must not leak any secret material.\n// The server is started in the background and a handle to it returned so it can be shut down.\nfunc httpserver(cp certProvider, sc secretChecker, sr secretRotator, burst int, rate int) *http.Server {\n\thttpRateLimiter := rateLimiter(burst, rate)\n\n\tmux := http.NewServeMux()\n\n\tmux.HandleFunc(\"/healthz\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"text/plain; charset=utf-8\")\n\t\t_, err := io.WriteString(w, \"ok\\n\")\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t})\n\n\tmux.Handle(\"/v1/verify\", Instrument(\"/v1/verify\", httpRateLimiter.RateLimit(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tcontent, err := io.ReadAll(r.Body)\n\t\tif err != nil {\n\t\t\tslog.Error(\"Error handling /v1/verify request\", \"error\", err)\n\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\tvalid, err := sc(content)\n\t\tif err != nil {\n\t\t\tslog.Error(\"Error validating secret\", \"error\", err)\n\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\n\t\tif valid {\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t} else {\n\t\t\tw.WriteHeader(http.StatusConflict)\n\t\t}\n\t}))))\n\n\t// TODO(mkm): rename to re-encrypt\n\tmux.Handle(\"/v1/rotate\", Instrument(\"/v1/rotate\", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tcontent, err := io.ReadAll(r.Body)\n\t\tif err != nil {\n\t\t\tslog.Error(\"Error handling /v1/rotate request\", \"error\", err)\n\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\tnewSecret, err := sr(content)\n\t\tif err != nil {\n\t\t\tslog.Error(\"Error rotating secret\", \"error\", err)\n\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\n\t\tw.WriteHeader(http.StatusOK)\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\t_, _ = w.Write(newSecret)\n\t})))\n\n\tmux.Handle(\"/v1/cert.pem\", Instrument(\"/v1/cert.pem\", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tcerts, err := cp()\n\t\tif err != nil {\n\t\t\tslog.Error(\"cannot get certificates\", \"error\", err)\n\t\t\thttp.Error(w, \"cannot get certificate\", http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\n\t\tw.Header().Set(\"Content-Type\", \"application/x-pem-file\")\n\t\tfor _, cert := range certs {\n\t\t\t_, _ = w.Write(pem.EncodeToMemory(&pem.Block{Type: certUtil.CertificateBlockType, Bytes: cert.Raw}))\n\t\t}\n\t})))\n\n\tserver := http.Server{\n\t\tAddr:              *listenAddr,\n\t\tHandler:           mux,\n\t\tReadTimeout:       *readTimeout,\n\t\tReadHeaderTimeout: *readTimeout,\n\t\tWriteTimeout:      *writeTimeout,\n\t}\n\n\tslog.Info(\"HTTP server serving\", \"addr\", server.Addr)\n\tgo func() {\n\t\terr := server.ListenAndServe()\n\t\tslog.Error(\"HTTP server exiting\", \"error\", err)\n\t}()\n\treturn &server\n}\n\nfunc httpserverMetrics() *http.Server {\n\tmux := http.NewServeMux()\n\tmux.Handle(\"/metrics\", promhttp.Handler())\n\n\tserver := http.Server{\n\t\tAddr:              *listenMetricsAddr,\n\t\tHandler:           mux,\n\t\tReadTimeout:       *readTimeout,\n\t\tReadHeaderTimeout: *readTimeout,\n\t\tWriteTimeout:      *writeTimeout,\n\t}\n\n\tslog.Info(\"HTTP metrics server serving\", \"addr\", server.Addr)\n\tgo func() {\n\t\terr := server.ListenAndServe()\n\t\tslog.Error(\"HTTP metrics server exiting\", \"error\", err)\n\t}()\n\treturn &server\n}\n\nfunc rateLimiter(burst int, rate int) throttled.HTTPRateLimiter {\n\tstore, err := memstore.New(65536)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tquota := throttled.RateQuota{MaxRate: throttled.PerSec(rate), MaxBurst: burst}\n\trateLimiter, err := throttled.NewGCRARateLimiter(store, quota)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\treturn throttled.HTTPRateLimiter{\n\t\tRateLimiter: rateLimiter,\n\t\tVaryBy:      &throttled.VaryBy{Path: true, Headers: []string{\"X-Forwarded-For\"}},\n\t}\n}\n"
  },
  {
    "path": "pkg/controller/server_test.go",
    "content": "package controller\n\nimport (\n\t\"context\"\n\t\"crypto/x509\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\tcertUtil \"k8s.io/client-go/util/cert\"\n)\n\ntype testCertStore struct {\n\tsync.Mutex\n\tcert *x509.Certificate\n}\n\nfunc (c *testCertStore) getCert() ([]*x509.Certificate, error) {\n\tc.Lock()\n\tdefer c.Unlock()\n\treturn []*x509.Certificate{c.cert}, nil\n}\n\nfunc (c *testCertStore) setCert(cert *x509.Certificate) {\n\tc.Lock()\n\tdefer c.Unlock()\n\tc.cert = cert\n}\n\nfunc shutdownServer(server *http.Server, t *testing.T) {\n\terr := server.Shutdown(context.Background())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestHttpCert(t *testing.T) {\n\tvalidFor := time.Hour\n\tcn := \"my-cn\"\n\t_, certBefore, err := generatePrivateKeyAndCert(2048, validFor, cn)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t_, certAfter, err := generatePrivateKeyAndCert(2048, validFor, cn)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcs := &testCertStore{}\n\tserver := httpserver(cs.getCert, nil, nil, 2, 2)\n\tdefer shutdownServer(server, t)\n\thp := *listenAddr\n\tif strings.HasPrefix(hp, \":\") {\n\t\thp = fmt.Sprintf(\"localhost%s\", hp)\n\t}\n\n\ttime.Sleep(1 * time.Second) // TODO(mkm) find a better way, e.g. retries\n\n\tcheck := func(cert *x509.Certificate) {\n\t\tresp, err := http.Get(fmt.Sprintf(\"http://%s/v1/cert.pem\", hp))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif got, want := resp.StatusCode, http.StatusOK; got != want {\n\t\t\tt.Fatalf(\"got: %v, want: %v\", got, want)\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tb, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tcerts, err := certUtil.ParseCertsPEM(b)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif got, want := len(certs), 1; got != want {\n\t\t\tt.Fatalf(\"got: %v, want: %v\", got, want)\n\t\t}\n\t\tif got, want := certs[0], cert; !got.Equal(want) {\n\t\t\tt.Fatalf(\"got: %v, want: %v\", got, want)\n\t\t}\n\t}\n\n\tcs.setCert(certBefore)\n\tcheck(certBefore)\n\n\tcs.setCert(certAfter)\n\tcheck(certAfter)\n}\n"
  },
  {
    "path": "pkg/controller/signal_notwin.go",
    "content": "//go:build !windows\n// +build !windows\n\npackage controller\n\nimport (\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n)\n\nfunc initKeyGenSignalListener(trigger func()) {\n\tsigChannel := make(chan os.Signal, 1)\n\tsignal.Notify(sigChannel, syscall.SIGUSR1)\n\tgo func() {\n\t\tfor {\n\t\t\t<-sigChannel\n\t\t\ttrigger()\n\t\t}\n\t}()\n}\n"
  },
  {
    "path": "pkg/controller/signal_windows.go",
    "content": "package controller\n\nfunc initKeyGenSignalListener(trigger func()) {}\n"
  },
  {
    "path": "pkg/crypto/crypto.go",
    "content": "package crypto\n\nimport (\n\t\"crypto/aes\"\n\t\"crypto/cipher\"\n\t\"crypto/rsa\"\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"golang.org/x/crypto/ssh\"\n)\n\nconst (\n\tsessionKeyBytes = 32\n)\n\n// ErrTooShort indicates the provided data is too short to be valid.\nvar ErrTooShort = errors.New(\"SealedSecret data is too short\")\n\n// PublicKeyFingerprint returns a fingerprint for a public key.\nfunc PublicKeyFingerprint(rp *rsa.PublicKey) (string, error) {\n\tsp, err := ssh.NewPublicKey(rp)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn ssh.FingerprintSHA256(sp), nil\n}\n\n// HybridEncrypt performs a regular AES-GCM + RSA-OAEP encryption.\n// The output byte string is:\n//\n//\tRSA ciphertext length || RSA ciphertext || AES ciphertext\nfunc HybridEncrypt(rnd io.Reader, pubKey *rsa.PublicKey, plaintext, label []byte) ([]byte, error) {\n\t// Generate a random symmetric key\n\tsessionKey := make([]byte, sessionKeyBytes)\n\tif _, err := io.ReadFull(rnd, sessionKey); err != nil {\n\t\treturn nil, err\n\t}\n\n\tblock, err := aes.NewCipher(sessionKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\taed, err := cipher.NewGCM(block)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Encrypt symmetric key\n\trsaCiphertext, err := rsa.EncryptOAEP(sha256.New(), rnd, pubKey, sessionKey, label)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// First 2 bytes are RSA ciphertext length, so we can separate\n\t// all the pieces later.\n\tciphertext := make([]byte, 2)\n\t// #nosec G115\n\tbinary.BigEndian.PutUint16(ciphertext, uint16(len(rsaCiphertext)))\n\tciphertext = append(ciphertext, rsaCiphertext...)\n\n\t// SessionKey is only used once, so zero nonce is ok\n\tzeroNonce := make([]byte, aed.NonceSize())\n\n\t// Append symmetrically encrypted Secret\n\tciphertext = aed.Seal(ciphertext, zeroNonce, plaintext, nil)\n\n\treturn ciphertext, nil\n}\n\n// HybridDecrypt performs a regular AES-GCM + RSA-OAEP decryption.\n// The private keys map has a fingerprint of each public key as the map key.\nfunc HybridDecrypt(rnd io.Reader, privKeys map[string]*rsa.PrivateKey, ciphertext, label []byte) ([]byte, error) {\n\t// TODO(mkm): use the key fingerprint encoded in ciphertext (if present) instead of trying all the possible keys\n\tfor _, privKey := range privKeys {\n\t\tif secret, err := singleDecrypt(rnd, privKey, ciphertext, label); err == nil {\n\t\t\treturn secret, nil\n\t\t}\n\t}\n\treturn nil, fmt.Errorf(\"no key could decrypt secret\")\n}\n\n// singleDecrypt performs a regular AES-GCM + RSA-OAEP decryption.\nfunc singleDecrypt(rnd io.Reader, privKey *rsa.PrivateKey, ciphertext, label []byte) ([]byte, error) {\n\tif len(ciphertext) < 2 {\n\t\treturn nil, ErrTooShort\n\t}\n\trsaLen := int(binary.BigEndian.Uint16(ciphertext))\n\tif len(ciphertext) < rsaLen+2 {\n\t\treturn nil, ErrTooShort\n\t}\n\n\trsaCiphertext := ciphertext[2 : rsaLen+2]\n\taesCiphertext := ciphertext[rsaLen+2:]\n\n\tsessionKey, err := rsa.DecryptOAEP(sha256.New(), rnd, privKey, rsaCiphertext, label)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tblock, err := aes.NewCipher(sessionKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\taed, err := cipher.NewGCM(block)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Key is only used once, so zero nonce is ok\n\tzeroNonce := make([]byte, aed.NonceSize())\n\n\tplaintext, err := aed.Open(nil, zeroNonce, aesCiphertext, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn plaintext, nil\n}\n"
  },
  {
    "path": "pkg/crypto/keys.go",
    "content": "package crypto\n\nimport (\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"io\"\n\t\"math/big\"\n\t\"time\"\n)\n\n// GeneratePrivateKeyAndCert generates a keypair and signed certificate.\nfunc GeneratePrivateKeyAndCert(keySize int, validFor time.Duration, cn string) (*rsa.PrivateKey, *x509.Certificate, error) {\n\tr := rand.Reader\n\tprivKey, err := rsa.GenerateKey(r, keySize)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tcert, err := SignKey(r, privKey, validFor, cn)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\treturn privKey, cert, nil\n}\n\n// SignKey returns a signed certificate.\nfunc SignKey(r io.Reader, key *rsa.PrivateKey, validFor time.Duration, cn string) (*x509.Certificate, error) {\n\t// TODO: use certificates API to get this signed by the cluster root CA\n\t// See https://kubernetes.io/docs/tasks/tls/managing-tls-in-a-cluster/\n\n\treturn SignKeyWithNotBefore(r, key, time.Now(), validFor, cn)\n}\n\n// SignKeyWithNotBefore returns a signed certificate with custom notBefore.\nfunc SignKeyWithNotBefore(r io.Reader, key *rsa.PrivateKey, notBefore time.Time, validFor time.Duration, cn string) (*x509.Certificate, error) {\n\t// TODO: use certificates API to get this signed by the cluster root CA\n\t// See https://kubernetes.io/docs/tasks/tls/managing-tls-in-a-cluster/\n\n\tserialNo, err := rand.Int(r, new(big.Int).Lsh(big.NewInt(1), 128))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcert := x509.Certificate{\n\t\tSerialNumber: serialNo,\n\t\tKeyUsage:     x509.KeyUsageEncipherOnly,\n\t\tNotBefore:    notBefore.UTC(),\n\t\tNotAfter:     notBefore.Add(validFor).UTC(),\n\t\tIssuer: pkix.Name{\n\t\t\tCommonName: cn,\n\t\t},\n\t\tSubject: pkix.Name{\n\t\t\tCommonName: cn,\n\t\t},\n\t\tBasicConstraintsValid: true,\n\t\tIsCA:                  true,\n\t}\n\n\tdata, err := x509.CreateCertificate(r, &cert, &cert, &key.PublicKey, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn x509.ParseCertificate(data)\n}\n"
  },
  {
    "path": "pkg/crypto/keys_test.go",
    "content": "package crypto\n\nimport (\n\t\"crypto/rsa\"\n\t\"io\"\n\tmathrand \"math/rand\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n)\n\n// This is omg-not safe for real crypto use!\nfunc testRand() io.Reader {\n\treturn mathrand.New(mathrand.NewSource(42))\n}\n\nfunc TestSignKey(t *testing.T) {\n\trand := testRand()\n\n\tkey, err := rsa.GenerateKey(rand, 2048)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to generate test key: %v\", err)\n\t}\n\n\tcert, err := SignKey(rand, key, time.Hour, \"mycn\")\n\tif err != nil {\n\t\tt.Errorf(\"signKey() returned error: %v\", err)\n\t}\n\n\tif !reflect.DeepEqual(cert.PublicKey, &key.PublicKey) {\n\t\tt.Errorf(\"cert pubkey != original pubkey\")\n\t}\n}\n"
  },
  {
    "path": "pkg/flagenv/flagenv.go",
    "content": "// Package flagenv implements a simple way to expose all your flags as environmental variables.\n//\n// Commandline flags have more precedence over environment variables.\n// In order to use it just call flagenv.SetFlagsFromEnv from an init function or from your main.\n//\n// You can call it either before or after your your flag.Parse invocation.\n//\n// This example will make it possible to set the default of --my_flag also via the MY_PROG_MY_FLAG\n// env var:\n//\n//\tvar myflag = flag.String(\"my_flag\", \"\", \"some flag\")\n//\n//\tfunc init() {\n//\t    flagenv.SetFlagsFromEnv(\"MY_PROG\", flag.CommandLine)\n//\t}\n//\n//\tfunc main() {\n//\t    flags.Parse()\n//\t    ...\n//\t}\npackage flagenv\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n)\n\n// SetFlagsFromEnv sets flag values from environment, e.g. PREFIX_FOO_BAR set -foo_bar.\n// It sets only flags that haven't been set explicitly. The defaults are preserved and -help\n// will still show the defaults provided in the code.\nfunc SetFlagsFromEnv(prefix string, fs *flag.FlagSet) {\n\tset := map[string]bool{}\n\tfs.Visit(func(f *flag.Flag) {\n\t\tset[f.Name] = true\n\t})\n\tfs.VisitAll(func(f *flag.Flag) {\n\t\t// ignore flags set from the commandline\n\t\tif set[f.Name] {\n\t\t\treturn\n\t\t}\n\t\t// remove trailing _ to reduce common errors with the prefix, i.e. people setting it to MY_PROG_\n\t\tcleanPrefix := strings.TrimSuffix(prefix, \"_\")\n\t\tname := fmt.Sprintf(\"%s_%s\", cleanPrefix, strings.Replace(strings.ToUpper(f.Name), \"-\", \"_\", -1))\n\t\tif e, ok := os.LookupEnv(name); ok {\n\t\t\t_ = f.Value.Set(e)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "pkg/flagenv/flagenv_test.go",
    "content": "package flagenv_test\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/flagenv\"\n)\n\nfunc TestFlagenv(t *testing.T) {\n\ttestCases := []struct {\n\t\tset  bool\n\t\tval  string\n\t\twant string\n\t}{\n\t\t{false, \"\", \"default\"},\n\t\t{true, \"bar\", \"bar\"},\n\t\t{true, \"\", \"\"},\n\t}\n\tfor i, tc := range testCases {\n\t\tt.Run(fmt.Sprint(i), func(t *testing.T) {\n\t\t\tdefer os.Unsetenv(\"MY_TEST_FOO\")\n\n\t\t\tif tc.set {\n\t\t\t\tos.Setenv(\"MY_TEST_FOO\", tc.val)\n\t\t\t}\n\n\t\t\tfs := flag.NewFlagSet(\"test\", flag.PanicOnError)\n\t\t\ts := fs.String(\"foo\", \"default\", \"help\")\n\t\t\tflagenv.SetFlagsFromEnv(\"MY_TEST\", fs)\n\n\t\t\t_ = fs.Parse(nil)\n\n\t\t\tif got, want := *s, tc.want; got != want {\n\t\t\t\tt.Errorf(\"got %q, want %q\", got, want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/kubeseal/kubeseal.go",
    "content": "package kubeseal\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"reflect\"\n\t\"strings\"\n\t\"time\"\n\n\t\"k8s.io/apimachinery/pkg/util/yaml\"\n\n\tssv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/apis/sealedsecrets/v1alpha1\"\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/crypto\"\n\tv1 \"k8s.io/api/core/v1\"\n\tk8serrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\truntimeserializer \"k8s.io/apimachinery/pkg/runtime/serializer\"\n\t\"k8s.io/apimachinery/pkg/util/net\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\tcorev1 \"k8s.io/client-go/kubernetes/typed/core/v1\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/util/cert\"\n\t\"k8s.io/client-go/util/keyutil\"\n)\n\ntype ClientConfig interface {\n\tClientConfig() (*rest.Config, error)\n\tNamespace() (string, bool, error)\n}\n\nfunc ParseKey(r io.Reader) (*rsa.PublicKey, error) {\n\tdata, err := io.ReadAll(r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcerts, err := cert.ParseCertsPEM(data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// ParseCertsPem returns error if len(certs) == 0, but best to be sure...\n\tif len(certs) == 0 {\n\t\treturn nil, errors.New(\"failed to read any certificates\")\n\t}\n\n\tcert, ok := certs[0].PublicKey.(*rsa.PublicKey)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"expected RSA public key but found %v\", certs[0].PublicKey)\n\t}\n\n\tif time.Now().After(certs[0].NotAfter) {\n\t\treturn nil, fmt.Errorf(\"failed to encrypt using an expired certificate on %v\", certs[0].NotAfter.Format(\"January 2, 2006\"))\n\t}\n\n\treturn cert, nil\n}\n\nfunc prettyEncoder(codecs runtimeserializer.CodecFactory, mediaType string, gv runtime.GroupVersioner) (runtime.Encoder, error) {\n\tinfo, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"binary can't serialize %s\", mediaType)\n\t}\n\n\tprettyEncoder := info.PrettySerializer\n\tif prettyEncoder == nil {\n\t\tprettyEncoder = info.Serializer\n\t}\n\n\tenc := codecs.EncoderForVersion(prettyEncoder, gv)\n\treturn enc, nil\n}\n\nfunc isFilename(name string) (bool, error) {\n\tu, err := url.Parse(name)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\t// windows drive letters\n\tif s := strings.ToLower(u.Scheme); len(s) == 1 && s[0] >= 'a' && s[0] <= 'z' {\n\t\treturn true, nil\n\t}\n\treturn u.Scheme == \"\", nil\n}\n\n// getServicePortName obtains the SealedSecrets service port name.\nfunc getServicePortName(ctx context.Context, client corev1.CoreV1Interface, namespace, serviceName string) (string, error) {\n\tservice, err := client.Services(namespace).Get(ctx, serviceName, metav1.GetOptions{})\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"cannot get sealed secret service: %v.\\nPlease, use the flag --controller-name and --controller-namespace to set up the name and namespace of the sealed secrets controller\", err)\n\t}\n\treturn service.Spec.Ports[0].Name, nil\n}\n\n// openCertLocal opens a cert URI or local filename, by fetching it locally from the client\n// (as opposed as openCertCluster which fetches it via HTTP but through the k8s API proxy).\nfunc openCertLocal(filenameOrURI string) (io.ReadCloser, error) {\n\t// detect if a certificate is a local file or an URI.\n\tif ok, err := isFilename(filenameOrURI); err != nil {\n\t\treturn nil, err\n\t} else if ok {\n\t\t// #nosec G304 -- should open user provided file\n\t\treturn os.Open(filenameOrURI)\n\t}\n\treturn openCertURI(filenameOrURI)\n}\n\nfunc openCertURI(uri string) (io.ReadCloser, error) {\n\t// support file:// scheme. Note: we're opening the file using os.Open rather\n\t// than using the file:// scheme below because there is no point in complicating our lives\n\t// and escape the filename properly.\n\n\tt := &http.Transport{Proxy: http.ProxyFromEnvironment}\n\t// #nosec: G111 -- we want to allow all files to be opened\n\tt.RegisterProtocol(\"file\", http.NewFileTransport(http.Dir(\"/\")))\n\tc := &http.Client{Transport: t}\n\n\tresp, err := c.Get(uri)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"cannot fetch %q: %s\", uri, resp.Status)\n\t}\n\treturn resp.Body, nil\n}\n\n// openCertCluster fetches a certificate by performing an HTTP request to the controller\n// through the k8s API proxy.\nfunc openCertCluster(ctx context.Context, c corev1.CoreV1Interface, namespace, name string) (io.ReadCloser, error) {\n\tportName, err := getServicePortName(ctx, c, namespace, name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcert, err := c.Services(namespace).ProxyGet(\"http\", name, portName, \"/v1/cert.pem\", nil).Stream(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot fetch certificate: %v\", err)\n\t}\n\treturn cert, nil\n}\n\nfunc OpenCert(ctx context.Context, clientConfig ClientConfig, controllerNs, controllerName string, certURL string) (io.ReadCloser, error) {\n\tif certURL != \"\" {\n\t\treturn openCertLocal(certURL)\n\t}\n\n\tconf, err := clientConfig.ClientConfig()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconf.AcceptContentTypes = \"application/x-pem-file, */*\"\n\trestClient, err := corev1.NewForConfig(conf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn openCertCluster(ctx, restClient, controllerNs, controllerName)\n}\n\nfunc readSecrets(r io.Reader) ([]*v1.Secret, error) {\n\tdecoder := yaml.NewYAMLOrJSONDecoder(r, 4096)\n\n\tvar secrets []*v1.Secret\n\tempty := v1.Secret{}\n\n\tfor {\n\t\tsec := v1.Secret{}\n\t\terr := decoder.Decode(&sec)\n\t\tif reflect.DeepEqual(sec, empty) {\n\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\tbreak\n\t\t\t} else {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tsecrets = append(secrets, &sec)\n\t\tif err != nil && err != io.EOF {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn secrets, nil\n}\n\nfunc readSealedSecrets(r io.Reader) ([]*ssv1alpha1.SealedSecret, error) {\n\tdecoder := yaml.NewYAMLOrJSONDecoder(r, 4096)\n\n\tvar secrets []*ssv1alpha1.SealedSecret\n\tempty := ssv1alpha1.SealedSecret{}\n\n\tfor {\n\t\tsec := ssv1alpha1.SealedSecret{}\n\t\terr := decoder.Decode(&sec)\n\t\tif reflect.DeepEqual(sec, empty) {\n\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\tbreak\n\t\t\t} else {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tsecrets = append(secrets, &sec)\n\t\tif err != nil && err != io.EOF {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn secrets, nil\n}\n\n// Seal reads a k8s Secret resource parsed from an input reader by a given codec, encrypts all its secrets\n// with a given public key, using the name and namespace found in the input secret, unless explicitly overridden\n// by the overrideName and overrideNamespace arguments.\nfunc Seal(clientConfig ClientConfig, outputFormat string, in io.Reader, out io.Writer, codecs runtimeserializer.CodecFactory, pubKey *rsa.PublicKey, scope ssv1alpha1.SealingScope, allowEmptyData bool, overrideName, overrideNamespace string) error {\n\tsecrets, err := readSecrets(in)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(secrets) == 0 {\n\t\treturn fmt.Errorf(\"no secrets found. Ensure the input is valid and UTF-8 encoded\")\n\t}\n\n\tfor _, secret := range secrets {\n\t\tif len(secret.Data) == 0 && len(secret.StringData) == 0 && !allowEmptyData {\n\t\t\treturn fmt.Errorf(\"secret.data is empty in input Secret, assuming this is an error and aborting. To work with empty data, --allow-empty-data can be used\")\n\t\t}\n\n\t\tif overrideName != \"\" {\n\t\t\tsecret.Name = overrideName\n\t\t}\n\n\t\tif secret.GetName() == \"\" {\n\t\t\treturn fmt.Errorf(\"missing metadata.name in input Secret\")\n\t\t}\n\n\t\tif overrideNamespace != \"\" {\n\t\t\tsecret.Namespace = overrideNamespace\n\t\t}\n\n\t\tif scope != ssv1alpha1.DefaultScope {\n\t\t\tsecret.Annotations = ssv1alpha1.UpdateScopeAnnotations(secret.Annotations, scope)\n\t\t}\n\n\t\tif ssv1alpha1.SecretScope(secret) != ssv1alpha1.ClusterWideScope {\n\t\t\tns, namespaceSet, _ := clientConfig.Namespace()\n\t\t\t// Check for namespace mismatch when namespace is explicitly set via command line\n\t\t\tif namespaceSet && secret.GetNamespace() != \"\" && secret.GetNamespace() != ns {\n\t\t\t\treturn fmt.Errorf(\"namespace mismatch: input secret is in namespace %q but %q was specified\", secret.GetNamespace(), ns)\n\t\t\t}\n\n\t\t\tif secret.GetNamespace() == \"\" {\n\t\t\t\tsecret.SetNamespace(ns)\n\t\t\t}\n\t\t}\n\n\t\t// Strip read-only server-side ObjectMeta (if present)\n\t\tsecret.SetSelfLink(\"\")\n\t\tsecret.SetUID(\"\")\n\t\tsecret.SetResourceVersion(\"\")\n\t\tsecret.Generation = 0\n\t\tsecret.SetCreationTimestamp(metav1.Time{})\n\t\tsecret.SetDeletionTimestamp(nil)\n\t\tsecret.DeletionGracePeriodSeconds = nil\n\n\t\tssecret, err := ssv1alpha1.NewSealedSecret(codecs, pubKey, secret)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err = sealedSecretOutput(out, outputFormat, codecs, ssecret); err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// return nil\n\t}\n\treturn nil\n}\n\nfunc ValidateSealedSecret(ctx context.Context, clientConfig ClientConfig, controllerNs, controllerName string, in io.Reader) error {\n\tconf, err := clientConfig.ClientConfig()\n\tif err != nil {\n\t\treturn err\n\t}\n\trestClient, err := corev1.NewForConfig(conf)\n\tif err != nil {\n\t\treturn err\n\t}\n\tportName, err := getServicePortName(ctx, restClient, controllerNs, controllerName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treq := restClient.RESTClient().Post().\n\t\tNamespace(controllerNs).\n\t\tResource(\"services\").\n\t\tSubResource(\"proxy\").\n\t\tName(net.JoinSchemeNamePort(\"http\", controllerName, portName)).\n\t\tSuffix(\"/v1/verify\")\n\n\tsecrets, err := readSealedSecrets(in)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to decrypt sealed secret\")\n\t}\n\n\tfor _, secret := range secrets {\n\t\tcontent, err := json.Marshal(secret)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error while marshalling sealed secret: %w\", err)\n\t\t}\n\t\treq.Body(content)\n\t\tres := req.Do(ctx)\n\t\tif err := res.Error(); err != nil {\n\t\t\tif status, ok := err.(*k8serrors.StatusError); ok && status.Status().Code == http.StatusConflict {\n\t\t\t\treturn fmt.Errorf(\"unable to decrypt sealed secret: %v\", secret.GetName())\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"cannot validate sealed secret: %v\", err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc ReEncryptSealedSecret(ctx context.Context, clientConfig ClientConfig, controllerNs, controllerName, outputFormat string, in io.Reader, out io.Writer, codecs runtimeserializer.CodecFactory) error {\n\tconf, err := clientConfig.ClientConfig()\n\tif err != nil {\n\t\treturn err\n\t}\n\trestClient, err := corev1.NewForConfig(conf)\n\tif err != nil {\n\t\treturn err\n\t}\n\tportName, err := getServicePortName(ctx, restClient, controllerNs, controllerName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treq := restClient.RESTClient().Post().\n\t\tNamespace(controllerNs).\n\t\tResource(\"services\").\n\t\tSubResource(\"proxy\").\n\t\tName(net.JoinSchemeNamePort(\"http\", controllerName, portName)).\n\t\tSuffix(\"/v1/rotate\")\n\n\tsecrets, err := readSealedSecrets(in)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, secret := range secrets {\n\t\tcontent, err := json.Marshal(secret)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treq.Body(content)\n\t\tres := req.Do(ctx)\n\t\tif err := res.Error(); err != nil {\n\t\t\tif status, ok := err.(*k8serrors.StatusError); ok && status.Status().Code == http.StatusConflict {\n\t\t\t\treturn fmt.Errorf(\"unable to rotate secret\")\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"cannot re-encrypt secret: %v\", err)\n\t\t}\n\t\tbody, err := res.Raw()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tssecret := &ssv1alpha1.SealedSecret{}\n\t\tif err = json.Unmarshal(body, ssecret); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tssecret.SetCreationTimestamp(metav1.Time{})\n\t\tssecret.SetDeletionTimestamp(nil)\n\t\tssecret.Generation = 0\n\t\tif err = sealedSecretOutput(out, outputFormat, codecs, ssecret); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc resourceOutput(out io.Writer, outputFormat string, codecs runtimeserializer.CodecFactory, gv runtime.GroupVersioner, obj runtime.Object) error {\n\tvar contentType string\n\tswitch strings.ToLower(outputFormat) {\n\tcase \"json\", \"\":\n\t\tcontentType = runtime.ContentTypeJSON\n\tcase \"yaml\":\n\t\tcontentType = runtime.ContentTypeYAML\n\t\tfmt.Fprint(out, \"---\\n\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported output format: %s\", outputFormat)\n\t}\n\tprettyEnc, err := prettyEncoder(codecs, contentType, gv)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbuf, err := runtime.Encode(prettyEnc, obj)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, _ = out.Write(buf)\n\n\tif contentType == runtime.ContentTypeJSON {\n\t\tfmt.Fprint(out, \"\\n\")\n\t}\n\treturn nil\n}\n\nfunc sealedSecretOutput(out io.Writer, outputFormat string, codecs runtimeserializer.CodecFactory, ssecret *ssv1alpha1.SealedSecret) error {\n\treturn resourceOutput(out, outputFormat, codecs, ssv1alpha1.SchemeGroupVersion, ssecret)\n}\n\nfunc decodeSealedSecret(codecs runtimeserializer.CodecFactory, b []byte) (*ssv1alpha1.SealedSecret, error) {\n\tvar ss ssv1alpha1.SealedSecret\n\tif err := runtime.DecodeInto(codecs.UniversalDecoder(), b, &ss); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &ss, nil\n}\n\nfunc SealMergingInto(clientConfig ClientConfig, outputFormat string, in io.Reader, filename string, codecs runtimeserializer.CodecFactory, pubKey *rsa.PublicKey, scope ssv1alpha1.SealingScope, allowEmptyData bool) error {\n\t// #nosec G304 -- should open user provided file\n\tf, err := os.OpenFile(filename, os.O_RDWR, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// #nosec G307 -- we are explicitly managing a potential error from f.Close() at the end of the function\n\tdefer f.Close()\n\n\tb, err := io.ReadAll(f)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\torig, err := decodeSealedSecret(codecs, b)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar buf bytes.Buffer\n\tif err := Seal(clientConfig, outputFormat, in, &buf, codecs, pubKey, scope, allowEmptyData, orig.Name, orig.Namespace); err != nil {\n\t\treturn err\n\t}\n\n\tupdate, err := decodeSealedSecret(codecs, buf.Bytes())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// merge encrypted data and metadata\n\tfor k, v := range update.Spec.EncryptedData {\n\t\torig.Spec.EncryptedData[k] = v\n\t}\n\tfor k, v := range update.Spec.Template.Annotations {\n\t\torig.Spec.Template.Annotations[k] = v\n\t}\n\tfor k, v := range update.Spec.Template.Labels {\n\t\torig.Spec.Template.Labels[k] = v\n\t}\n\tfor k, v := range update.Spec.Template.Data {\n\t\torig.Spec.Template.Data[k] = v\n\t}\n\n\t// updated sealed secret file in-place avoiding clobbering the file upon rendering errors.\n\tvar out bytes.Buffer\n\tif err := sealedSecretOutput(&out, outputFormat, codecs, orig); err != nil {\n\t\treturn err\n\t}\n\n\tif err := f.Truncate(0); err != nil {\n\t\treturn err\n\t}\n\tif _, err := f.Seek(0, 0); err != nil {\n\t\treturn err\n\t}\n\tif _, err := io.Copy(f, &out); err != nil {\n\t\treturn err\n\t}\n\t// we explicitly call f.Close() to return a potential error when closing the file that wouldn't be returned in the deferred f.Close()\n\tif err := f.Close(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc EncryptSecretItem(w io.Writer, secretName, ns string, data []byte, scope ssv1alpha1.SealingScope, pubKey *rsa.PublicKey) error {\n\t// TODO(mkm): refactor cluster-wide/namespace-wide to an actual enum so we can have a simple flag\n\t// to refer to the scope mode that is not a tuple of booleans.\n\tlabel := ssv1alpha1.EncryptionLabel(ns, secretName, scope)\n\tout, err := crypto.HybridEncrypt(rand.Reader, pubKey, data, label)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Fprint(w, base64.StdEncoding.EncodeToString(out))\n\treturn nil\n}\n\n// parseFromFile parses a value of the kubectl --from-file flag, which can optionally include an item name\n// preceding the first equals sign.\nfunc ParseFromFile(s string) (string, string) {\n\tc := strings.SplitN(s, \"=\", 2)\n\tif len(c) == 1 {\n\t\treturn \"\", c[0]\n\t}\n\treturn c[0], c[1]\n}\n\nfunc readPrivKeysFromFile(filename string) ([]*rsa.PrivateKey, error) {\n\t// #nosec G304 -- should open user provided file\n\tb, err := os.ReadFile(filename)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tres, err := parsePrivKey(b)\n\tif err == nil {\n\t\treturn []*rsa.PrivateKey{res}, nil\n\t}\n\n\tvar secrets []*v1.Secret\n\n\t// try to parse it as json/yaml encoded v1.List of secrets\n\tvar lst v1.List\n\tif err = runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), b, &lst); err == nil {\n\t\tfor _, r := range lst.Items {\n\t\t\ts, err := readSecrets(bytes.NewBuffer(r.Raw))\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tsecrets = append(secrets, s...)\n\t\t}\n\t} else {\n\t\t// try to parse it as json/yaml encoded secret\n\t\ts, err := readSecrets(bytes.NewBuffer(b))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tsecrets = append(secrets, s...)\n\t}\n\n\tvar keys []*rsa.PrivateKey\n\tfor _, s := range secrets {\n\t\ttlsKey, ok := s.Data[\"tls.key\"]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"secret must contain a 'tls.data' key\")\n\t\t}\n\t\tpk, err := parsePrivKey(tlsKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tkeys = append(keys, pk)\n\t}\n\n\treturn keys, nil\n}\n\nfunc readPrivKey(filename string) (*rsa.PrivateKey, error) {\n\tpks, err := readPrivKeysFromFile(filename)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn pks[0], nil\n}\n\nfunc parsePrivKey(b []byte) (*rsa.PrivateKey, error) {\n\tkey, err := keyutil.ParsePrivateKeyPEM(b)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch rsaKey := key.(type) {\n\tcase *rsa.PrivateKey:\n\t\treturn rsaKey, nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unexpected private key type %T\", key)\n\t}\n}\n\nfunc readPrivKeys(filenames []string) (map[string]*rsa.PrivateKey, error) {\n\tres := map[string]*rsa.PrivateKey{}\n\tfor _, filename := range filenames {\n\t\tpks, err := readPrivKeysFromFile(filename)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfor _, pk := range pks {\n\t\t\tfingerprint, err := crypto.PublicKeyFingerprint(&pk.PublicKey)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tres[fingerprint] = pk\n\t\t}\n\t}\n\treturn res, nil\n}\n\nfunc UnsealSealedSecret(w io.Writer, in io.Reader, privKeysFilenames []string, outputFormat string, codecs runtimeserializer.CodecFactory) error {\n\tprivKeys, err := readPrivKeys(privKeysFilenames)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tb, err := io.ReadAll(in)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tss, err := decodeSealedSecret(codecs, b)\n\tif err != nil {\n\t\treturn err\n\t}\n\tsec, err := ss.Unseal(codecs, privKeys)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn resourceOutput(w, outputFormat, codecs, v1.SchemeGroupVersion, sec)\n}\n"
  },
  {
    "path": "pkg/kubeseal/kubeseal_test.go",
    "content": "package kubeseal\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/big\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"path/filepath\"\n\tgoruntime \"runtime\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/util/yaml\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\tclientcmdapi \"k8s.io/client-go/tools/clientcmd/api\"\n\t\"k8s.io/client-go/util/keyutil\"\n\t\"k8s.io/utils/strings/slices\"\n\n\tssv1alpha1 \"github.com/bitnami-labs/sealed-secrets/pkg/apis/sealedsecrets/v1alpha1\"\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/crypto\"\n\tcertUtil \"k8s.io/client-go/util/cert\"\n)\n\nconst testCert = `\n-----BEGIN CERTIFICATE-----\nMIIErTCCApWgAwIBAgIQBekz48i8NbrzIpIrLMIULTANBgkqhkiG9w0BAQsFADAA\nMB4XDTE3MDYyMDA0MzI0NVoXDTI3MDYxODA0MzI0NVowADCCAiIwDQYJKoZIhvcN\nAQEBBQADggIPADCCAgoCggIBAL6ISW4MnHAmC6MdmJOwo9C6YYhKYDwPD2tF+j4p\nI2duB3y7DLF+zWNHgbUlBZck8CudacJTuxOJFEqr4umqm0f4EGgRPwZgFvFLHKSZ\n/hxUFnMcGVhY1qsk55peSghPHarOYyBhhHDtCu7qdMu9MqPZB68y16HdPvwWPadI\ndBKSxDLvwYfjDnG/ZHX9rmlDKej7jPGdvqAY5VJteP30w6YHb1Uc4whppNcDSc2l\ngOuKAWtQ5WfZbB0NpMhj4framNeXMYwjZytEdC1c/4O45zm5eK4FNPueCfxOlzFQ\nD3y34OuQlJwlrPE4KmdMHtE1a8x0ihbglInJrtqcXK3vEdUJ2c/BKWgFtPOTz6Du\njV4j0OMVVGnk5jUmh+yfbgielIkPcpSTWP1cIPwK3eWbrvMziq6sv0x7QoOD3Pzm\nGBE8Y9sa5uy+bJZt5MywbamZ3xWaxoQbSN8RPoxRhTe0DEpx6utCXSWpapT7kWZ3\nR1PTuVx+Ktyz7MRoDUWvxfpMJ2hsJ71Az0AuUZ4N4fmmGdUcM81GPUOiMZ4uqySQ\nA2phgikbJaTzcT85RcNFYSi4eKc5mYFNqr5xVa6uHhZ+OGeGy1yyOEWLgIZV3A/8\n4eZshOyYtRlZjCkaGZTfXNft+8QJi8rEZRcJtVhqLzezBVRsL7pt6P/mQj4+XHsE\nVSBrAgMBAAGjIzAhMA4GA1UdDwEB/wQEAwIAATAPBgNVHRMBAf8EBTADAQH/MA0G\nCSqGSIb3DQEBCwUAA4ICAQCSizqBB3bjHCSGk/8lpqIyHJQR5u4Cf7LRrC9U8mxe\npvC3Fx3/RlVe87Y4cUb37xZc/TmB6Bq10Y6R7ydS3oe8PCh4UQRnEfBgtJ6m59ha\nt3iPX0NdQVYz/D+yEiHjpI7gpyFNuGkd4/78JE51SO4yGYvWk/ChHoMvbLcxzfdK\nPI2Ymf3MWtGfoF/TQ1jy/Biy+qumDPSz23MynQG39cdUInSK26oemUbTH0koLulN\nfNl4TwSEdSm2DRl0la+vkrzu7SvF9SJ2ES6wMWVjYiJLNpApjGuF9/ZOFw9DvSSH\nm+UYXn+IC7rTgvXKvXTlG//z/14Lx0GFIY+ZjdENwLH//orBQLg37TZatKEpaWO6\nuRzFUxZVw3ic3RxoHfEbRA9vQlQdKnV+BpZe/Pb08RAh82OZyujqqyK7cPPOW5Vi\nT9y+NeMwfKH8H4un7mQWkgWFw3LMIspYY5uHWp6jBwU9u/mjoK4+Y219dkaAhAcx\nD+YIZRXwxc6ehLCavGF2DIepybzDlJbiCe8JxUDsrE/Xkm6x28uq35oZ3UQznubU\n7LfAeRSI99sNvFnq0TqhSlp+CUDs8Z1LvDXzAHX4UeZQl4g+H+w1KudCvjO0mPPp\nR9bIjJLIvp7CQPDkdRzJSjvetrKtI0l97VjsjbRB9v6ZekGY9SFI49KzKUTk8fsF\n/A==\n-----END CERTIFICATE-----\n`\n\nvar (\n\ttestModulus  *big.Int\n\ttestExponent = 65537\n)\n\nfunc init() {\n\ttestModulus = new(big.Int)\n\t_, err := fmt.Sscan(\"777304254876434297689544225447769213262492599515515837291621795936355252933930193245809942636192119684040605554803489669141565417296821660595336672178414512660751886699171738066307588619202437848899334837760648051656982184646490661921128886671800776058692981991859399404705935722225294811424879738586269551402668122524371718537515440568440102201259925611463161144897905846190044735554045001999198442528435295995584980713050916813579912296878368079243909549993116827192901474611239264189340401059113919551426849847211275352102674049634252149163111599977742365280992561904350781270344655927564475032580504276518647106167707150111291732645399166011800154961975117045723373023335778593638216165426988399138193230056486079421256484837299169853958601000282124667227789126483641999102102039577368681983584245367307077546423870452524154641890843463963116237003367269116435430641427113406369059991147359641266708862913786891945896441771663010146473536372286482453315017377528517965715554550898957321536181165129538808789201530141159181590893764287807749414277289452691723903046140558704697831351834538780165261072894792900501671534138992265545905216973214953125367388406669893889742303072755608685449114438926280862339744991872488262084141163\", testModulus)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc tmpfile(t *testing.T, contents []byte) string {\n\tf, err := os.CreateTemp(\"\", \"testdata\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create tempfile: %v\", err)\n\t}\n\tif _, err := f.Write(contents); err != nil {\n\t\tt.Fatalf(\"Failed to write to tempfile: %v\", err)\n\t}\n\tif err := f.Close(); err != nil {\n\t\tt.Fatalf(\"Failed to close tempfile: %v\", err)\n\t}\n\treturn f.Name()\n}\n\nfunc TestParseKey(t *testing.T) {\n\tkey, err := ParseKey(strings.NewReader(testCert))\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to parse test key: %v\", err)\n\t}\n\n\tif key.N.Cmp(testModulus) != 0 {\n\t\tt.Errorf(\"Unexpected key modulus: %v\", key.N)\n\t}\n\n\tif key.E != testExponent {\n\t\tt.Errorf(\"Unexpected key exponent: %v\", key.E)\n\t}\n}\n\n/* repeated from main here... STARTs */\n\nfunc testClientConfig() clientcmd.ClientConfig {\n\treturn &mockClientConfig{namespace: \"testns\", namespaceSet: false}\n}\n\n/* repeated from main here... ENDs */\n\nfunc TestOpenCertFile(t *testing.T) {\n\tctx := context.Background()\n\tclientConfig := testClientConfig()\n\tcontrollerNs := \"default\"\n\tcontrollerName := \"controller\"\n\tcertFile := tmpfile(t, []byte(testCert))\n\n\ts := httptest.NewServer(http.FileServer(http.Dir(filepath.Dir(certFile))))\n\tdefer s.Close()\n\n\ttestCases := []string{\n\t\tcertFile,\n\t\tfmt.Sprintf(\"%s/%s\", s.URL, filepath.Base(certFile)),\n\t\t// This should work on windows but it causes a 500 error in the file handler. TODO: investigate\n\t\t//\t\t(&url.URL{Scheme: \"file\", Path: path.Join(\"/\", filepath.ToSlash(certFile))}).String(),\n\t}\n\tif goruntime.GOOS != \"windows\" {\n\t\ttestCases = append(testCases, fmt.Sprintf(\"file://%s\", certFile))\n\t}\n\n\tfor _, certURL := range testCases {\n\t\tf, err := OpenCert(ctx, clientConfig, controllerNs, controllerName, certURL)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Error reading test cert file: %v\", err)\n\t\t}\n\n\t\tdata, err := io.ReadAll(f)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Error reading from test cert file: %v\", err)\n\t\t}\n\n\t\tif string(data) != testCert {\n\t\t\tt.Errorf(\"Read incorrect data from cert file?!\")\n\t\t}\n\t}\n}\n\nfunc TestSealWithMultiDocSecrets(t *testing.T) {\n\tkey, err := ParseKey(strings.NewReader(testCert))\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to parse gotSecrets key: %v\", err)\n\t}\n\n\ttestCases := []struct {\n\t\tname           string\n\t\tasYaml         bool\n\t\tinputSeparator string\n\t\toutputFormat   string\n\t}{\n\t\t{\n\t\t\tname:           \"multi-doc json\",\n\t\t\tasYaml:         false,\n\t\t\tinputSeparator: \"\\n\",\n\t\t\toutputFormat:   \"json\",\n\t\t},\n\t\t{\n\t\t\tname:           \"multi-doc yaml\",\n\t\t\tasYaml:         true,\n\t\t\tinputSeparator: \"---\\n\",\n\t\t\toutputFormat:   \"yaml\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ts1 := mkTestSecret(t, \"foo\", \"1\", withSecretName(\"s1\"), asYAML(tc.asYaml))\n\t\t\ts2 := mkTestSecret(t, \"bar\", \"2\", withSecretName(\"s2\"), asYAML(tc.asYaml))\n\t\t\tmultiDocYaml := fmt.Sprintf(\"%s%s%s\", s1, tc.inputSeparator, s2)\n\n\t\t\tclientConfig := &mockClientConfig{namespace: \"testns\", namespaceSet: false}\n\t\t\toutputFormat := tc.outputFormat\n\t\t\tinbuf := bytes.Buffer{}\n\t\t\t_, err = bytes.NewBuffer([]byte(multiDocYaml)).WriteTo(&inbuf)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Error writing to buffer: %v\", err)\n\t\t\t}\n\n\t\t\tt.Logf(\"input is:\\n%s\", inbuf.String())\n\n\t\t\toutbuf := bytes.Buffer{}\n\t\t\tif err := Seal(clientConfig, outputFormat, &inbuf, &outbuf, scheme.Codecs, key, ssv1alpha1.NamespaceWideScope, false, \"\", \"\"); err != nil {\n\t\t\t\tt.Fatalf(\"seal() returned error: %v\", err)\n\t\t\t}\n\n\t\t\toutBytes := outbuf.Bytes()\n\t\t\tt.Logf(\"output is:\\n%s\", outBytes)\n\n\t\t\tif tc.asYaml {\n\t\t\t\tif !strings.HasPrefix(string(outBytes), \"---\") {\n\t\t\t\t\tt.Errorf(\"YAML output should start with ---\")\n\t\t\t\t}\n\n\t\t\t\tif strings.HasSuffix(string(outBytes), \"---\\n\") {\n\t\t\t\t\tt.Errorf(\"YAML output should not end with ---\")\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdecoder := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(outBytes), 4096)\n\t\t\tvar gotSecrets []*ssv1alpha1.SealedSecret\n\t\t\tfor {\n\t\t\t\ts := ssv1alpha1.SealedSecret{}\n\t\t\t\terr := decoder.Decode(&s)\n\t\t\t\tif err != nil {\n\t\t\t\t\tif err == io.EOF {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tt.Fatalf(\"Failed to parse result: %v\", err)\n\t\t\t\t}\n\t\t\t\tgotSecrets = append(gotSecrets, &s)\n\t\t\t}\n\n\t\t\tif got, want := len(gotSecrets), 2; got != want {\n\t\t\t\tt.Errorf(\"Wrong element output length: got: %d, want: %d\", got, want)\n\t\t\t}\n\n\t\t\tfor _, gotSecret := range gotSecrets {\n\t\t\t\tif got, want := gotSecret.GetNamespace(), \"testns\"; got != want {\n\t\t\t\t\tt.Errorf(\"got: %q, want: %q\", got, want)\n\t\t\t\t}\n\t\t\t\tif got, want := gotSecret.GetName(), []string{\"s1\", \"s2\"}; !slices.Contains(want, got) {\n\t\t\t\t\tt.Errorf(\"got: %q, want: %q\", got, want)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSeal(t *testing.T) {\n\tkey, err := ParseKey(strings.NewReader(testCert))\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to parse test key: %v\", err)\n\t}\n\n\ttestCases := []struct {\n\t\tsecret v1.Secret\n\t\tscope  ssv1alpha1.SealingScope\n\t\twant   ssv1alpha1.SealedSecret // partial object\n\t}{\n\t\t{\n\t\t\tsecret: v1.Secret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"mysecret\",\n\t\t\t\t\tNamespace: \"myns\",\n\t\t\t\t},\n\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\"foo\": []byte(\"sekret\"),\n\t\t\t\t},\n\t\t\t\tStringData: map[string]string{\n\t\t\t\t\t\"foos\": \"stringsekret\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: ssv1alpha1.SealedSecret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"mysecret\",\n\t\t\t\t\tNamespace: \"myns\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsecret: v1.Secret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName: \"mysecret\",\n\t\t\t\t},\n\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\"foo\": []byte(\"sekret\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: ssv1alpha1.SealedSecret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"mysecret\",\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsecret: v1.Secret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"mysecret\",\n\t\t\t\t\tNamespace: \"\",\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\tssv1alpha1.SealedSecretNamespaceWideAnnotation: \"true\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\"foo\": []byte(\"sekret\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: ssv1alpha1.SealedSecret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"mysecret\",\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\tssv1alpha1.SealedSecretNamespaceWideAnnotation: \"true\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsecret: v1.Secret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"mysecret\",\n\t\t\t\t\tNamespace: \"\",\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\tssv1alpha1.SealedSecretClusterWideAnnotation: \"true\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\"foo\": []byte(\"sekret\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: ssv1alpha1.SealedSecret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"mysecret\",\n\t\t\t\t\tNamespace: \"\", // <--- we shouldn't force the default namespace for cluster wide secrets ...\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\tssv1alpha1.SealedSecretClusterWideAnnotation: \"true\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsecret: v1.Secret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"mysecret\",\n\t\t\t\t\tNamespace: \"myns\",\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\tssv1alpha1.SealedSecretClusterWideAnnotation: \"true\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\"foo\": []byte(\"sekret\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: ssv1alpha1.SealedSecret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"mysecret\",\n\t\t\t\t\tNamespace: \"myns\", // <--- ... but we should preserve one if specified.\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\tssv1alpha1.SealedSecretClusterWideAnnotation: \"true\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsecret: v1.Secret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"mysecret\",\n\t\t\t\t\tNamespace: \"\",\n\t\t\t\t},\n\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\"foo\": []byte(\"sekret\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tscope: ssv1alpha1.NamespaceWideScope,\n\t\t\twant: ssv1alpha1.SealedSecret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"mysecret\",\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\tssv1alpha1.SealedSecretNamespaceWideAnnotation: \"true\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsecret: v1.Secret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"mysecret\",\n\t\t\t\t\tNamespace: \"\",\n\t\t\t\t},\n\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\"foo\": []byte(\"sekret\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tscope: ssv1alpha1.ClusterWideScope,\n\t\t\twant: ssv1alpha1.SealedSecret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"mysecret\",\n\t\t\t\t\tNamespace: \"\",\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\tssv1alpha1.SealedSecretClusterWideAnnotation: \"true\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor i, tc := range testCases {\n\t\tt.Run(fmt.Sprint(i), func(t *testing.T) {\n\t\t\tclientConfig := &mockClientConfig{namespace: \"testns\", namespaceSet: false}\n\t\t\t// For test cases where the secret has no namespace and we expect it to be filled with \"default\"\n\t\t\tif tc.secret.GetNamespace() == \"\" && tc.want.GetNamespace() == \"default\" {\n\t\t\t\tclientConfig = &mockClientConfig{namespace: \"default\", namespaceSet: true}\n\t\t\t}\n\t\t\toutputFormat := \"json\"\n\t\t\tinfo, ok := runtime.SerializerInfoForMediaType(scheme.Codecs.SupportedMediaTypes(), runtime.ContentTypeJSON)\n\t\t\tif !ok {\n\t\t\t\tt.Fatalf(\"binary can't serialize JSON\")\n\t\t\t}\n\t\t\tenc := scheme.Codecs.EncoderForVersion(info.Serializer, v1.SchemeGroupVersion)\n\t\t\tinbuf := bytes.Buffer{}\n\t\t\tif err := enc.Encode(&tc.secret, &inbuf); err != nil {\n\t\t\t\tt.Fatalf(\"Error encoding: %v\", err)\n\t\t\t}\n\n\t\t\tt.Logf(\"input is: %s\", inbuf.String())\n\n\t\t\toutbuf := bytes.Buffer{}\n\t\t\tif err := Seal(clientConfig, outputFormat, &inbuf, &outbuf, scheme.Codecs, key, tc.scope, false, \"\", \"\"); err != nil {\n\t\t\t\tt.Fatalf(\"seal() returned error: %v\", err)\n\t\t\t}\n\n\t\t\toutBytes := outbuf.Bytes()\n\t\t\tt.Logf(\"output is %s\", outBytes)\n\n\t\t\tvar result ssv1alpha1.SealedSecret\n\t\t\tif err = runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), outBytes, &result); err != nil {\n\t\t\t\tt.Fatalf(\"Failed to parse result: %v\", err)\n\t\t\t}\n\n\t\t\tsmeta := result.GetObjectMeta()\n\t\t\tif got, want := smeta.GetName(), tc.want.GetName(); got != want {\n\t\t\t\tt.Errorf(\"got: %q, want: %q\", got, want)\n\t\t\t}\n\t\t\tif got, want := smeta.GetNamespace(), tc.want.GetNamespace(); got != want {\n\t\t\t\tt.Errorf(\"got: %q, want: %q\", got, want)\n\t\t\t}\n\t\t\tif got, want := smeta.GetAnnotations(), tc.want.GetAnnotations(); !cmp.Equal(got, want, cmpopts.EquateEmpty()) {\n\t\t\t\tt.Errorf(\"got: %q, want: %q\", got, want)\n\t\t\t}\n\n\t\t\tfor n := range tc.secret.Data {\n\t\t\t\tif len(result.Spec.EncryptedData[n]) < 100 {\n\t\t\t\t\tt.Errorf(\"Encrypted data is implausibly short: %v\", result.Spec.EncryptedData[n])\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor n := range tc.secret.StringData {\n\t\t\t\tif len(result.Spec.EncryptedData[n]) < 100 {\n\t\t\t\t\tt.Errorf(\"Encrypted data is implausibly short: %v\", result.Spec.EncryptedData[n])\n\t\t\t\t}\n\t\t\t}\n\t\t\t// NB: See sealedsecret_test.go for e2e crypto test\n\t\t})\n\t}\n}\n\ntype mkTestSecretOpt func(*mkTestSecretOpts)\ntype mkTestSecretOpts struct {\n\tsecretName      string\n\tsecretNamespace string\n\tasYAML          bool\n}\n\nfunc withSecretName(n string) mkTestSecretOpt {\n\treturn func(o *mkTestSecretOpts) {\n\t\to.secretName = n\n\t}\n}\n\nfunc withSecretNamespace(n string) mkTestSecretOpt {\n\treturn func(o *mkTestSecretOpts) {\n\t\to.secretNamespace = n\n\t}\n}\n\nfunc asYAML(y bool) mkTestSecretOpt {\n\treturn func(o *mkTestSecretOpts) {\n\t\to.asYAML = y\n\t}\n}\n\nfunc mkTestSecret(t *testing.T, key, value string, opts ...mkTestSecretOpt) []byte {\n\to := mkTestSecretOpts{\n\t\tsecretName:      \"testname\",\n\t\tsecretNamespace: \"testns\",\n\t}\n\tfor _, opt := range opts {\n\t\topt(&o)\n\t}\n\n\tsecret := v1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      o.secretName,\n\t\t\tNamespace: o.secretNamespace,\n\t\t\tAnnotations: map[string]string{\n\t\t\t\tkey: value, // putting secret here just to have a simple way to test annotation merges\n\t\t\t},\n\t\t\tLabels: map[string]string{\n\t\t\t\tkey: value,\n\t\t\t},\n\t\t},\n\t\tData: map[string][]byte{\n\t\t\tkey: []byte(value),\n\t\t},\n\t}\n\n\tcontentType := runtime.ContentTypeJSON\n\tif o.asYAML {\n\t\tcontentType = runtime.ContentTypeYAML\n\t}\n\n\tinfo, ok := runtime.SerializerInfoForMediaType(scheme.Codecs.SupportedMediaTypes(), contentType)\n\tif !ok {\n\t\tt.Fatalf(\"binary can't serialize JSON\")\n\t}\n\tenc := scheme.Codecs.EncoderForVersion(info.Serializer, v1.SchemeGroupVersion)\n\tvar inbuf bytes.Buffer\n\tif err := enc.Encode(&secret, &inbuf); err != nil {\n\t\tt.Fatalf(\"Error encoding: %v\", err)\n\t}\n\treturn inbuf.Bytes()\n}\n\nfunc mkTestSealedSecret(t *testing.T, pubKey *rsa.PublicKey, key, value string, opts ...mkTestSecretOpt) []byte {\n\tclientConfig := &mockClientConfig{namespace: \"testns\", namespaceSet: false}\n\toutputFormat := \"json\"\n\tinbuf := bytes.NewBuffer(mkTestSecret(t, key, value, opts...))\n\tvar outbuf bytes.Buffer\n\tif err := Seal(clientConfig, outputFormat, inbuf, &outbuf, scheme.Codecs, pubKey, ssv1alpha1.DefaultScope, false, \"\", \"\"); err != nil {\n\t\tt.Fatalf(\"seal() returned error: %v\", err)\n\t}\n\n\treturn outbuf.Bytes()\n}\n\n// TODO(mkm): rename newTestKeyPair to newTestKeyPairs.\nfunc newTestKeyPair(t *testing.T) (*rsa.PublicKey, map[string]*rsa.PrivateKey) {\n\tprivKey, _, err := crypto.GeneratePrivateKeyAndCert(2048, time.Hour, \"testcn\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tpubKey := &privKey.PublicKey\n\n\tfp, err := crypto.PublicKeyFingerprint(pubKey)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tprivKeys := map[string]*rsa.PrivateKey{fp: privKey}\n\n\treturn pubKey, privKeys\n}\n\nfunc TestUnseal(t *testing.T) {\n\tpubKey, privKeys := newTestKeyPair(t)\n\tpkFile, err := os.CreateTemp(\"\", \"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.RemoveAll(pkFile.Name())\n\n\tif len(privKeys) != 1 {\n\t\tt.Fatal(\"assuming only one test key-pair\")\n\t}\n\tfor _, key := range privKeys {\n\t\terr := pem.Encode(pkFile, &pem.Block{Type: keyutil.RSAPrivateKeyBlockType, Bytes: x509.MarshalPKCS1PrivateKey(key)})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\tpkFile.Close()\n\n\tconst (\n\t\tsecretItemKey   = \"foo\"\n\t\tsecretItemValue = \"secret1\"\n\t)\n\tss := mkTestSealedSecret(t, pubKey, secretItemKey, secretItemValue)\n\n\tvar buf bytes.Buffer\n\tprivKeysList := []string{pkFile.Name()}\n\toutputFormat := \"json\"\n\tif err := UnsealSealedSecret(&buf, bytes.NewBuffer(ss), privKeysList, outputFormat, scheme.Codecs); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tsecret, err := readSecrets(&buf)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor _, secret := range secret {\n\t\tif got, want := string(secret.Data[secretItemKey]), secretItemValue; got != want {\n\t\t\tt.Fatalf(\"got: %q, want: %q\", got, want)\n\t\t}\n\t}\n}\n\nfunc TestUnsealList(t *testing.T) {\n\tpubKey, privKeys := newTestKeyPair(t)\n\tpkFile, err := os.CreateTemp(\"\", \"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.RemoveAll(pkFile.Name())\n\n\t// encode a v1.List containing all the privKeys into one file.\n\tprettyEnc, err := prettyEncoder(scheme.Codecs, runtime.ContentTypeJSON, v1.SchemeGroupVersion)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar secrets [][]byte\n\tfor _, key := range privKeys {\n\t\tb := pem.EncodeToMemory(&pem.Block{Type: keyutil.RSAPrivateKeyBlockType, Bytes: x509.MarshalPKCS1PrivateKey(key)})\n\t\tbuf, err := runtime.Encode(prettyEnc, &v1.Secret{Data: map[string][]byte{\"tls.key\": b}})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tsecrets = append(secrets, buf)\n\t}\n\tlst := &v1.List{}\n\tfor _, s := range secrets {\n\t\tlst.Items = append(lst.Items, runtime.RawExtension{Raw: s})\n\t}\n\tblst, err := runtime.Encode(prettyEnc, lst)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif _, err := pkFile.Write(blst); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tpkFile.Close()\n\n\tconst (\n\t\tsecretItemKey   = \"foo\"\n\t\tsecretItemValue = \"secret1\"\n\t)\n\tss := mkTestSealedSecret(t, pubKey, secretItemKey, secretItemValue)\n\n\tvar buf bytes.Buffer\n\tprivKeysList := []string{pkFile.Name()}\n\toutputFormat := \"json\"\n\tif err := UnsealSealedSecret(&buf, bytes.NewBuffer(ss), privKeysList, outputFormat, scheme.Codecs); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tsecret, err := readSecrets(&buf)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor _, secret := range secret {\n\t\tif got, want := string(secret.Data[secretItemKey]), secretItemValue; got != want {\n\t\t\tt.Fatalf(\"got: %q, want: %q\", got, want)\n\t\t}\n\t}\n}\n\nfunc TestMergeInto(t *testing.T) {\n\tclientConfig := &mockClientConfig{namespace: \"testns\", namespaceSet: false}\n\toutputFormat := \"json\"\n\tpubKey, privKeys := newTestKeyPair(t)\n\n\tmerge := func(t *testing.T, newSecret, oldSealedSecret []byte) *ssv1alpha1.SealedSecret {\n\t\tf, err := os.CreateTemp(\"\", \"*.json\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif _, err := f.Write(oldSealedSecret); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tf.Close()\n\n\t\tbuf := bytes.NewBuffer(newSecret)\n\t\tif err := SealMergingInto(clientConfig, outputFormat, buf, f.Name(), scheme.Codecs, pubKey, ssv1alpha1.DefaultScope, false); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tb, err := os.ReadFile(f.Name())\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tmerged, err := decodeSealedSecret(scheme.Codecs, b)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t_, err = merged.Unseal(scheme.Codecs, privKeys)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\treturn merged\n\t}\n\n\tt.Run(\"added\", func(t *testing.T) {\n\t\tmerged := merge(t,\n\t\t\tmkTestSecret(t, \"foo\", \"secret1\"),\n\t\t\tmkTestSealedSecret(t, pubKey, \"bar\", \"secret2\"),\n\t\t)\n\n\t\tcheckAdded := func(m map[string]string, old, new string) {\n\t\t\tif got, want := len(m), 2; got != want {\n\t\t\t\tt.Fatalf(\"got: %d, want: %d\", got, want)\n\t\t\t}\n\n\t\t\tif _, ok := m[old]; !ok {\n\t\t\t\tt.Fatalf(\"cannot find expected key\")\n\t\t\t}\n\n\t\t\tif _, ok := m[new]; !ok {\n\t\t\t\tt.Fatalf(\"cannot find expected key\")\n\t\t\t}\n\t\t}\n\n\t\tcheckAdded(merged.Spec.EncryptedData, \"foo\", \"bar\")\n\t\tcheckAdded(merged.Spec.Template.Annotations, \"foo\", \"bar\")\n\t\tcheckAdded(merged.Spec.Template.Labels, \"foo\", \"bar\")\n\t})\n\n\tt.Run(\"updated\", func(t *testing.T) {\n\t\torigSrc := mkTestSealedSecret(t, pubKey, \"foo\", \"secret2\")\n\t\torig, err := decodeSealedSecret(scheme.Codecs, origSrc)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tmerged := merge(t,\n\t\t\tmkTestSecret(t, \"foo\", \"secret1\"),\n\t\t\torigSrc,\n\t\t)\n\n\t\tcheckUpdated := func(before, after map[string]string, key string) {\n\t\t\tif got, want := len(after), 1; got != want {\n\t\t\t\tt.Fatalf(\"got: %d, want: %d\", got, want)\n\t\t\t}\n\n\t\t\tif old, new := before[key], after[key]; old == new {\n\t\t\t\tt.Fatalf(\"expecting %q and %q to be different\", old, new)\n\t\t\t}\n\t\t}\n\n\t\tcheckUpdated(orig.Spec.EncryptedData, merged.Spec.EncryptedData, \"foo\")\n\t\tcheckUpdated(orig.Spec.Template.Annotations, merged.Spec.Template.Annotations, \"foo\")\n\t\tcheckUpdated(orig.Spec.Template.Labels, merged.Spec.Template.Labels, \"foo\")\n\t})\n\n\tt.Run(\"bad name\", func(t *testing.T) {\n\t\t// should not fail even if input has a bad secret name because the name in existing existing sealed secret\n\t\t// should win (same for namespace).\n\t\t// TODO(mkm): test for case with scope mismatch too.\n\t\tmerge(t,\n\t\t\tmkTestSecret(t, \"foo\", \"secret1\", withSecretName(\"badname\"), withSecretNamespace(\"badns\")),\n\t\t\tmkTestSealedSecret(t, pubKey, \"bar\", \"secret2\"),\n\t\t)\n\t})\n}\n\n// writeTempFile creates a temporary file, writes data into it and closes it.\nfunc writeTempFile(b []byte) (string, error) {\n\ttmp, err := os.CreateTemp(\"\", \"\")\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer tmp.Close()\n\n\tif _, err := tmp.Write(b); err != nil {\n\t\tos.RemoveAll(tmp.Name())\n\t\treturn \"\", err\n\t}\n\n\treturn tmp.Name(), nil\n}\n\n// testingKeypairFiles returns a path to a PEM encoded certificate and a PEM encoded private key\n// along with a function to be called to cleanup those files.\nfunc testingKeypairFiles(t *testing.T) (string, string, func()) {\n\t_, pk := newTestKeyPairSingle(t)\n\n\tcert, err := crypto.SignKey(rand.Reader, pk, time.Hour, \"testcn\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcertFile, err := writeTempFile(pem.EncodeToMemory(&pem.Block{Type: certUtil.CertificateBlockType, Bytes: cert.Raw}))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpkPEM, err := keyutil.MarshalPrivateKeyToPEM(pk)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tpkFile, err := writeTempFile(pkPEM)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn certFile, pkFile, func() {\n\t\tos.RemoveAll(certFile)\n\t\tos.RemoveAll(pkFile)\n\t}\n}\n\nfunc sealTestItem(certFilename, secretNS, secretName, secretValue string, scope ssv1alpha1.SealingScope) (string, error) {\n\tvar buf bytes.Buffer\n\n\tctx := context.Background()\n\tclientConfig := testClientConfig()\n\tcontrollerNs := \"default\"\n\tcontrollerName := \"controller\"\n\tf, err := OpenCert(ctx, clientConfig, controllerNs, controllerName, certFilename)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer f.Close()\n\tpubKey, err := ParseKey(f)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif err := EncryptSecretItem(&buf, secretName, secretNS, []byte(secretValue), scope, pubKey); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn buf.String(), nil\n}\n\nfunc TestRaw(t *testing.T) {\n\tcertFilename, privKeyFilename, cleanup := testingKeypairFiles(t)\n\tdefer cleanup()\n\n\tconst (\n\t\tsecretNS    = \"myns\"\n\t\tsecretName  = \"mysecret\"\n\t\tsecretItem  = \"foo\"\n\t\tsecretValue = \"supersecret\"\n\t)\n\n\ttestCases := []struct {\n\t\tns        string\n\t\tname      string\n\t\tscope     ssv1alpha1.SealingScope\n\t\tunsealErr string\n\t}{\n\t\t// strict scope\n\t\t{ns: secretNS, name: secretName},\n\t\t{ns: \"youGiveRest\", name: secretName, unsealErr: \"no key could decrypt secret\"},\n\t\t{ns: secretNS, name: \"aBadName\", unsealErr: \"no key could decrypt secret\"},\n\n\t\t// namespace-wide scope\n\t\t{scope: ssv1alpha1.NamespaceWideScope, ns: secretNS, name: secretName},\n\t\t{scope: ssv1alpha1.NamespaceWideScope, ns: \"youGiveRest\", unsealErr: \"no key could decrypt secret\"},\n\t\t{scope: ssv1alpha1.NamespaceWideScope, ns: \"youGiveRest\", name: \"aBadName\", unsealErr: \"no key could decrypt secret\"},\n\t\t{scope: ssv1alpha1.NamespaceWideScope, ns: secretNS, name: \"\"},\n\t\t{scope: ssv1alpha1.NamespaceWideScope, ns: secretNS, name: \"aBadName\"},\n\n\t\t// cluster-wide scope\n\t\t{scope: ssv1alpha1.ClusterWideScope, ns: secretNS, name: secretName},\n\t\t{scope: ssv1alpha1.ClusterWideScope, ns: \"youGiveRest\", name: secretName},\n\t\t{scope: ssv1alpha1.ClusterWideScope, ns: secretNS, name: \"\"},\n\t\t{scope: ssv1alpha1.ClusterWideScope, ns: secretNS, name: \"aBadName\"},\n\t\t{scope: ssv1alpha1.ClusterWideScope, ns: \"\", name: \"\"},\n\t\t{scope: ssv1alpha1.ClusterWideScope, ns: \"\", name: \"aBadName\"},\n\t}\n\n\tfor i, tc := range testCases {\n\t\t// encrypt an item with data from the testCase and put it\n\t\t// in a sealed secret with the metadata from the constants above\n\t\tt.Run(fmt.Sprint(i), func(t *testing.T) {\n\t\t\tenc, err := sealTestItem(certFilename, tc.ns, tc.name, secretValue, tc.scope)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tss := &ssv1alpha1.SealedSecret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tNamespace: secretNS,\n\t\t\t\t\tName:      secretName,\n\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\tfmt.Sprintf(\"sealedsecrets.bitnami.com/%s\", tc.scope.String()): \"true\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSpec: ssv1alpha1.SealedSecretSpec{\n\t\t\t\t\tEncryptedData: map[string]string{\n\t\t\t\t\t\tsecretItem: enc,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tprivKeys, err := readPrivKeys([]string{privKeyFilename})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tsec, err := ss.Unseal(scheme.Codecs, privKeys)\n\t\t\tif tc.unsealErr != \"\" {\n\t\t\t\tif got, want := err.Error(), tc.unsealErr; !strings.HasPrefix(got, want) {\n\t\t\t\t\tt.Fatalf(\"got: %v, want: %v\", err, want)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tif got, want := string(sec.Data[secretItem]), secretValue; got != want {\n\t\t\t\tt.Errorf(\"got: %q, want: %q\", got, want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc newTestKeyPairSingle(t *testing.T) (*rsa.PublicKey, *rsa.PrivateKey) {\n\tprivKey, _, err := crypto.GeneratePrivateKeyAndCert(2048, time.Hour, \"testcn\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn &privKey.PublicKey, privKey\n}\n\nfunc TestReadPrivKeySecret(t *testing.T) {\n\toutputFormat := \"json\"\n\t_, pkw := newTestKeyPairSingle(t)\n\n\tb, err := keyutil.MarshalPrivateKeyToPEM(pkw)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tsec := &v1.Secret{\n\t\tData: map[string][]byte{\n\t\t\t\"tls.key\": b,\n\t\t},\n\t}\n\n\ttmp, err := os.CreateTemp(\"\", \"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// defer os.RemoveAll(tmp.Name())\n\n\tif err := resourceOutput(tmp, outputFormat, scheme.Codecs, v1.SchemeGroupVersion, sec); err != nil {\n\t\tt.Fatal(err)\n\t}\n\ttmp.Close()\n\n\tpkr, err := readPrivKey(tmp.Name())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif got, want := pkr.D.String(), pkw.D.String(); got != want {\n\t\tt.Errorf(\"got: %q, want: %q\", got, want)\n\t}\n}\n\nfunc TestReadPrivKeyPEM(t *testing.T) {\n\t_, pkw := newTestKeyPairSingle(t)\n\n\tb, err := keyutil.MarshalPrivateKeyToPEM(pkw)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\ttmp, err := os.CreateTemp(\"\", \"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.RemoveAll(tmp.Name())\n\n\tif _, err := tmp.Write(b); err != nil {\n\t\tt.Fatal(err)\n\t}\n\ttmp.Close()\n\n\tpkr, err := readPrivKey(tmp.Name())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif got, want := pkr.D.String(), pkw.D.String(); got != want {\n\t\tt.Errorf(\"got: %q, want: %q\", got, want)\n\t}\n}\n\nfunc TestNamespaceMismatchValidation(t *testing.T) {\n\tkey, err := ParseKey(strings.NewReader(testCert))\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to parse test key: %v\", err)\n\t}\n\n\ttestCases := []struct {\n\t\tname            string\n\t\tsecret          v1.Secret\n\t\tconfigNamespace string\n\t\tnamespaceSet    bool\n\t\texpectedError   string\n\t}{\n\t\t{\n\t\t\tname: \"namespace mismatch should fail\",\n\t\t\tsecret: v1.Secret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"mysecret\",\n\t\t\t\t\tNamespace: \"secretns\",\n\t\t\t\t},\n\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\"foo\": []byte(\"sekret\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tconfigNamespace: \"flagns\",\n\t\t\tnamespaceSet:    true,\n\t\t\texpectedError:   \"namespace mismatch: input secret is in namespace \\\"secretns\\\" but \\\"flagns\\\" was specified\",\n\t\t},\n\t\t{\n\t\t\tname: \"matching namespaces should succeed\",\n\t\t\tsecret: v1.Secret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"mysecret\",\n\t\t\t\t\tNamespace: \"samens\",\n\t\t\t\t},\n\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\"foo\": []byte(\"sekret\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tconfigNamespace: \"samens\",\n\t\t\tnamespaceSet:    true,\n\t\t\texpectedError:   \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"no namespace flag set should succeed\",\n\t\t\tsecret: v1.Secret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"mysecret\",\n\t\t\t\t\tNamespace: \"secretns\",\n\t\t\t\t},\n\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\"foo\": []byte(\"sekret\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tconfigNamespace: \"flagns\",\n\t\t\tnamespaceSet:    false,\n\t\t\texpectedError:   \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"empty secret namespace with flag set should succeed\",\n\t\t\tsecret: v1.Secret{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"mysecret\",\n\t\t\t\t\tNamespace: \"\",\n\t\t\t\t},\n\t\t\t\tData: map[string][]byte{\n\t\t\t\t\t\"foo\": []byte(\"sekret\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tconfigNamespace: \"flagns\",\n\t\t\tnamespaceSet:    true,\n\t\t\texpectedError:   \"\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t// Create a mock client config that returns the test namespace\n\t\t\tmockClientConfig := &mockClientConfig{\n\t\t\t\tnamespace:    tc.configNamespace,\n\t\t\t\tnamespaceSet: tc.namespaceSet,\n\t\t\t}\n\n\t\t\toutputFormat := \"json\"\n\t\t\tinfo, ok := runtime.SerializerInfoForMediaType(scheme.Codecs.SupportedMediaTypes(), runtime.ContentTypeJSON)\n\t\t\tif !ok {\n\t\t\t\tt.Fatalf(\"binary can't serialize JSON\")\n\t\t\t}\n\t\t\tenc := scheme.Codecs.EncoderForVersion(info.Serializer, v1.SchemeGroupVersion)\n\t\t\tinbuf := bytes.Buffer{}\n\t\t\tif err := enc.Encode(&tc.secret, &inbuf); err != nil {\n\t\t\t\tt.Fatalf(\"Error encoding: %v\", err)\n\t\t\t}\n\n\t\t\toutbuf := bytes.Buffer{}\n\t\t\terr := Seal(mockClientConfig, outputFormat, &inbuf, &outbuf, scheme.Codecs, key, ssv1alpha1.DefaultScope, false, \"\", \"\")\n\n\t\t\tif tc.expectedError != \"\" {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Fatalf(\"Expected error %q but got nil\", tc.expectedError)\n\t\t\t\t}\n\t\t\t\tif got, want := err.Error(), tc.expectedError; got != want {\n\t\t\t\t\tt.Errorf(\"got error: %q, want: %q\", got, want)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\n// mockClientConfig implements clientcmd.ClientConfig for testing\ntype mockClientConfig struct {\n\tnamespace    string\n\tnamespaceSet bool\n}\n\nfunc (m *mockClientConfig) Namespace() (string, bool, error) {\n\treturn m.namespace, m.namespaceSet, nil\n}\n\nfunc (m *mockClientConfig) ClientConfig() (*rest.Config, error) {\n\treturn &rest.Config{}, nil\n}\n\nfunc (m *mockClientConfig) ConfigAccess() clientcmd.ConfigAccess {\n\treturn nil\n}\n\nfunc (m *mockClientConfig) RawConfig() (clientcmdapi.Config, error) {\n\treturn clientcmdapi.Config{}, nil\n}\n"
  },
  {
    "path": "pkg/log/log.go",
    "content": "package log\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"log/slog\"\n)\n\n// MultiStreamHandler slog handler for directing different\ntype MultiStreamHandler struct {\n\tlevel       slog.Level\n\tlowHandler  slog.Handler\n\thighHandler slog.Handler\n}\n\n// New returns new MultiStreamHandler\nfunc New(outLow, outHigh io.Writer, format string, opts *slog.HandlerOptions) *MultiStreamHandler {\n\tif format == \"json\" {\n\t\treturn &MultiStreamHandler{\n\t\t\tlevel:       opts.Level.Level(),\n\t\t\tlowHandler:  slog.NewJSONHandler(outLow, opts),\n\t\t\thighHandler: slog.NewJSONHandler(outHigh, opts),\n\t\t}\n\t}\n\treturn &MultiStreamHandler{\n\t\tlevel:       opts.Level.Level(),\n\t\tlowHandler:  slog.NewTextHandler(outLow, opts),\n\t\thighHandler: slog.NewTextHandler(outHigh, opts),\n\t}\n}\n\n// Enabled check if log level is enabled\nfunc (m *MultiStreamHandler) Enabled(ctx context.Context, level slog.Level) bool {\n\treturn level >= m.level.Level()\n}\n\n// Handle pass to Low or High handlers based on log level\nfunc (m *MultiStreamHandler) Handle(ctx context.Context, r slog.Record) error {\n\tif r.Level <= slog.LevelInfo.Level() {\n\t\treturn m.lowHandler.Handle(ctx, r)\n\t}\n\treturn m.highHandler.Handle(ctx, r)\n}\n\nfunc (m *MultiStreamHandler) WithAttrs(attrs []slog.Attr) slog.Handler {\n\t// Not used within the code\n\tpanic(\"Not implemented\")\n}\n\nfunc (m *MultiStreamHandler) WithGroup(string) slog.Handler {\n\t// Not used within the code\n\tpanic(\"Not implemented\")\n}\n"
  },
  {
    "path": "pkg/multidocyaml/multidocyaml.go",
    "content": "package multidocyaml\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\n\t\"gopkg.in/yaml.v2\"\n)\n\nfunc isMultiDocumentYAML(src []byte) bool {\n\tdec := yaml.NewDecoder(bytes.NewReader(src))\n\tvar dummy struct{}\n\t_ = dec.Decode(&dummy)\n\treturn dec.Decode(&dummy) == nil\n}\n\n// EnsureNotMultiDoc returns an error if the yaml.\nfunc EnsureNotMultiDoc(src []byte) error {\n\tif isMultiDocumentYAML(src) {\n\t\treturn fmt.Errorf(\"Multistream YAML not supported yet (see https://github.com/bitnami-labs/sealed-secrets/issues/114)\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/multidocyaml/multidocyaml_test.go",
    "content": "package multidocyaml\n\nimport \"testing\"\n\nfunc TestIsMultiDocumentYAML(t *testing.T) {\n\ttestCases := []struct {\n\t\tsrc string\n\t\tok  bool\n\t}{\n\t\t{\"foo\", false},\n\t\t{\"foo\\nbar\\n\", false},\n\t\t{\"---\\nfoo\", false},\n\t\t{\"foo\\n---\\n\", true},\n\t\t{\"foo\\n ---\\n\", false},\n\t\t{\"---\\nfoo\\n---\\n\", true},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tif got, want := isMultiDocumentYAML([]byte(tc.src)), tc.ok; got != want {\n\t\t\tt.Errorf(\"got: %v, want: %v (src: %q)\", got, want, tc.src)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/pflagenv/flagenv.go",
    "content": "// Package pflagenv implements a simple way to expose all your pflag flags as environmental variables.\n//\n// Commandline flags have more precedence over environment variables.\n// In order to use it just call pflagenv.SetFlagsFromEnv from an init function or from your main.\n//\n// You can call it either before or after your your flag.Parse invocation.\n//\n// This example will make it possible to set the default of --my_flag also via the MY_PROG_MY_FLAG\n// env var:\n//\n//\tvar myflag = pflag.String(\"my_flag\", \"\", \"some flag\")\n//\n//\tfunc init() {\n//\t    pflagenv.SetFlagsFromEnv(\"MY_PROG\", pflag.CommandLine)\n//\t}\n//\n//\tfunc main() {\n//\t    pflag.Parse()\n//\t    ...\n//\t}\npackage pflagenv\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\tflag \"github.com/spf13/pflag\"\n)\n\n// SetFlagsFromEnv sets flag values from environment, e.g. PREFIX_FOO_BAR set -foo_bar.\n// It sets only flags that haven't been set explicitly. The defaults are preserved and -help\n// will still show the defaults provided in the code.\nfunc SetFlagsFromEnv(prefix string, fs *flag.FlagSet) {\n\tset := map[string]bool{}\n\tfs.Visit(func(f *flag.Flag) {\n\t\tset[f.Name] = true\n\t})\n\tfs.VisitAll(func(f *flag.Flag) {\n\t\t// ignore flags set from the commandline\n\t\tif set[f.Name] {\n\t\t\treturn\n\t\t}\n\t\t// remove trailing _ to reduce common errors with the prefix, i.e. people setting it to MY_PROG_\n\t\tcleanPrefix := strings.TrimSuffix(prefix, \"_\")\n\t\tname := fmt.Sprintf(\"%s_%s\", cleanPrefix, strings.Replace(strings.ToUpper(f.Name), \"-\", \"_\", -1))\n\t\tif e, ok := os.LookupEnv(name); ok {\n\t\t\t_ = f.Value.Set(e)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "pkg/pflagenv/flagenv_test.go",
    "content": "package pflagenv_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/bitnami-labs/sealed-secrets/pkg/pflagenv\"\n\tflag \"github.com/spf13/pflag\"\n)\n\nfunc TestPflagenv(t *testing.T) {\n\ttestCases := []struct {\n\t\tset  bool\n\t\tval  string\n\t\twant string\n\t}{\n\t\t{false, \"\", \"default\"},\n\t\t{true, \"bar\", \"bar\"},\n\t\t{true, \"\", \"\"},\n\t}\n\tfor i, tc := range testCases {\n\t\tt.Run(fmt.Sprint(i), func(t *testing.T) {\n\t\t\tdefer os.Unsetenv(\"MY_TEST_FOO\")\n\n\t\t\tif tc.set {\n\t\t\t\tos.Setenv(\"MY_TEST_FOO\", tc.val)\n\t\t\t}\n\n\t\t\tfs := flag.NewFlagSet(\"test\", flag.PanicOnError)\n\t\t\ts := fs.String(\"foo\", \"default\", \"help\")\n\t\t\tpflagenv.SetFlagsFromEnv(\"MY_TEST\", fs)\n\n\t\t\t_ = fs.Parse(nil)\n\n\t\t\tif got, want := *s, tc.want; got != want {\n\t\t\t\tt.Errorf(\"got %q, want %q\", got, want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "schema-v1alpha1.yaml",
    "content": "openAPIV3Schema:\n  description: |-\n    SealedSecret is the K8s representation of a \"sealed Secret\" - a\n    regular k8s Secret that has been sealed (encrypted) using the\n    controller's key.\n  properties:\n    apiVersion:\n      description: |-\n        APIVersion defines the versioned schema of this representation of an object.\n        Servers should convert recognized schemas to the latest internal value, and\n        may reject unrecognized values.\n        More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n      type: string\n    kind:\n      description: |-\n        Kind is a string value representing the REST resource this object represents.\n        Servers may infer this from the endpoint the client submits requests to.\n        Cannot be updated.\n        In CamelCase.\n        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: SealedSecretSpec is the specification of a SealedSecret.\n      properties:\n        data:\n          description: Data is deprecated and will be removed eventually. Use per-value EncryptedData instead.\n          format: byte\n          type: string\n        encryptedData:\n          additionalProperties:\n            type: string\n          type: object\n          x-kubernetes-preserve-unknown-fields: true\n        template:\n          description: |-\n            Template defines the structure of the Secret that will be\n            created from this sealed secret.\n          properties:\n            data:\n              additionalProperties:\n                type: string\n              description: Keys that should be templated using decrypted data.\n              nullable: true\n              type: object\n            immutable:\n              description: |-\n                Immutable, if set to true, ensures that data stored in the Secret cannot\n                be updated (only object metadata can be modified).\n                If not set to true, the field can be modified at any time.\n                Defaulted to nil.\n              type: boolean\n            metadata:\n              description: |-\n                Standard object's metadata.\n                More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata\n              nullable: true\n              properties:\n                annotations:\n                  additionalProperties:\n                    type: string\n                  type: object\n                finalizers:\n                  items:\n                    type: string\n                  type: array\n                labels:\n                  additionalProperties:\n                    type: string\n                  type: object\n                name:\n                  type: string\n                namespace:\n                  type: string\n              type: object\n              x-kubernetes-preserve-unknown-fields: true\n            type:\n              description: Used to facilitate programmatic handling of secret data.\n              type: string\n          type: object\n      required:\n        - encryptedData\n      type: object\n    status:\n      description: SealedSecretStatus is the most recently observed status of the SealedSecret.\n      properties:\n        conditions:\n          description: Represents the latest available observations of a sealed secret's current state.\n          items:\n            description: SealedSecretCondition describes the state of a sealed secret at a certain point.\n            properties:\n              lastTransitionTime:\n                description: Last time the condition transitioned from one status to another.\n                format: date-time\n                type: string\n              lastUpdateTime:\n                description: The last time this condition was updated.\n                format: date-time\n                type: string\n              message:\n                description: A human readable message indicating details about the transition.\n                type: string\n              reason:\n                description: The reason for the condition's last transition.\n                type: string\n              status:\n                description: |-\n                  Status of the condition for a sealed secret.\n                  Valid values for \"Synced\": \"True\", \"False\", or \"Unknown\".\n                type: string\n              type:\n                description: |-\n                  Type of condition for a sealed secret.\n                  Valid value: \"Synced\"\n                type: string\n            required:\n              - status\n              - type\n            type: object\n          type: array\n        observedGeneration:\n          description: ObservedGeneration reflects the generation most recently observed by the sealed-secrets controller.\n          format: int64\n          type: integer\n      type: object\n  required:\n    - spec\n  type: object\n"
  },
  {
    "path": "scripts/check-k8s",
    "content": "#!/bin/bash\n\nset -euo pipefail\n\nexport K8S_CONTEXT=\"${K8S_CONTEXT}\"\n\nif kubectl config current-context > /dev/null ;then\n\tk8s_current_context=$(kubectl config current-context);\n\tif [ \"${k8s_current_context}\" != \"${K8S_CONTEXT}\" ]; then \\\n\t\techo \"Expected k8s context '${K8S_CONTEXT}' but got '${k8s_current_context}'\";\n\t\texit 1;\n\tfi\nelse\n\techo \"Set up your k8s config for '${K8S_CONTEXT}' (using minikube or kind for example)\";\n\texit 1;\nfi\n\necho \"'${K8S_CONTEXT}' is configured as kubectl's current context\"\n\n"
  },
  {
    "path": "scripts/kubeseal-sudo",
    "content": "#!/usr/bin/env bash\n\nset -o errexit\nset -o nounset\nset -o pipefail\n\n# Constants\nRESET='\\033[0m'\nGREEN='\\033[38;5;2m'\nRED='\\033[38;5;1m'\nYELLOW='\\033[38;5;3m'\n\n# Axiliar functions\n########################\n# Log message to stderr\n# Arguments:\n#   $1 - Message to log\n#########################\nlog() {\n  printf \"%b\\n\" \"${*}\" >&2\n}\n\n########################\n# Log error message\n# Arguments:\n#   $1 - Message to log\n#########################\nerror() {\n  log \"${RED}ERROR ${RESET} ==> ${*}\"\n}\n\n#########################\n# Redirects output to /dev/null unless debug mode is enabled\n# Globals:\n#   DEBUG_MODE\n# Arguments:\n#   $@ - Command to execute\n# Returns:\n#   None\n#########################\nsilence() {\n    if ${DEBUG_MODE:-false}; then\n        \"$@\"\n    else\n        \"$@\" >/dev/null 2>&1\n    fi\n}\n\nprint_menu() {\n    local script\n    script=$(basename \"${BASH_SOURCE[0]}\")\n    log \"${RED}NAME${RESET}\"\n    log \"    $(basename -s .sh \"${BASH_SOURCE[0]}\")\"\n    log \"\"\n    log \"${RED}SYNOPSIS${RESET}\"\n    log \"    $script [${YELLOW}-h${RESET}] [${YELLOW}-n ${GREEN}\\\"namespace\\\"${RESET}] [${YELLOW}-s ${GREEN}\\\"service_account\\\"${RESET}]\"\n    log \"\"\n    log \"${RED}DESCRIPTION${RESET}\"\n    log \"    Script to run kubeseal using a service account credentials.\"\n    log \"\"\n    log \"    The options are as follow:\"\n    log \"\"\n    log \"      ${YELLOW}-n, --namespace ${GREEN}[namespace]${RESET}                Namespace to use.\"\n    log \"      ${YELLOW}-s, --service-account ${GREEN}[service_account]${RESET}    ServiceAccount to use.\"\n    log \"\"\n    log \"${RED}EXAMPLES${RESET}\"\n    log \"      $script --help\"\n    log \"      $script --service-account \\\"sealed-secrets\\\"\"\n    log \"      $script --service-account \\\"sealed-secrets\\\" --namespace \\\"kube-system\\\"\"\n    log \"\"\n}\n\nnamespace=\"default\"\nservice_account=\"\"\nhelp_menu=0\nwhile [[ \"$#\" -gt 0 ]]; do\n    case \"$1\" in\n        -h|--help)\n            help_menu=1\n            ;;\n        -n|--namespace)\n            shift; namespace=\"${1:?missing namespace}\"\n            ;;\n        -s|--service-account)\n            shift; service_account=\"${1:?missing service account}\"\n            ;;\n        *)\n            error \"Invalid command line flag $1\" >&2\n            exit 1\n            ;;\n    esac\n    shift\ndone\n\nif [[ \"$help_menu\" -eq 1 ]]; then\n    print_menu\n    exit 0\nfi\n\nif [[ -z \"$service_account\" ]]; then\n    error \"Missing ServiceAccount\"\n    exit 1\nfi\n\nTMPKUBE=$(mktemp)\nkubectl config view --flatten --minify > \"$TMPKUBE\"\nexport KUBECONFIG=\"$TMPKUBE\"\nif ! silence kubectl --kubeconfig \"$TMPKUBE\" -n \"$namespace\" get sa \"$service_account\"; then\n    error \"Missing ServiceAccount \\\"$service_account\\\" in namespace \\\"$namespace\\\"\"\n    exit 1\nfi\nsa_secret=\"$(kubectl --kubeconfig \"$TMPKUBE\" -n \"$namespace\" get sa \"$service_account\" -o jsonpath='{.secrets[0].name}')\"\nsa_token=\"$(kubectl --kubeconfig \"$TMPKUBE\" -n \"$namespace\" get secret \"$sa_secret\" -o jsonpath='{.data.token}')\"\nsilence kubectl --kubeconfig \"$TMPKUBE\" config set-credentials \"kubesudo:$namespace:$service_account\" --token=\"$(echo \"$sa_token\" | base64 --decode)\"\nsilence kubectl --kubeconfig \"$TMPKUBE\" config set-context \"$(kubectl config current-context)\" --user=\"kubesudo:$namespace:$service_account\"\n# We assume the controller is running in the same namespace as the ServiceAccount\n# and the controller service name is the same used for the ServiceAccount name\nkubeseal --controller-name=\"$service_account\" --controller-namespace=\"$namespace\" \"$@\"\nrm \"$TMPKUBE\"\n"
  },
  {
    "path": "scripts/release-check",
    "content": "#!/usr/bin/env bash\n\nset -o nounset\n\nfunction docker_tag_exists() {\n\tdocker pull $1:$2 > /dev/null\n}\n\nfunction find_release() {\n\tcurl -v --silent https://github.com/bitnami-labs/sealed-secrets/releases 2>&1 | grep -w kubeseal-$1 > /dev/null\n\techo $?\n}\n\n\nRELEASE=$(find_release $2)\nif [ $RELEASE -ne 0 ] ; then\n\tif docker_tag_exists $1 $2; then\n\t\techo 1\n\telse\n\t\techo 0\n\tfi\nelse\n\techo 0\nfi\n"
  },
  {
    "path": "site/.gitignore",
    "content": ".vagrant\n.DS_Store\n.sass-cache\n_ignore\nnode_modules\n_site\n.jekyll\n.jekyll-metadata\n.bundle\n.vscode\n\n*.log\n*.js.map\n*.css.map\n.ruby-version\npublic"
  },
  {
    "path": "site/README.md",
    "content": "# Website for [Sealed Secrets](https://sealed-secrets.netlify.app/)\n\n## Deployment\n\nThe website will be deployed to production each time a new commit gets merged into the main branch. It is deployed using Netlify.\n\n## Requirements\n\nThis site uses [Hugo](https://github.com/gohugoio/hugo) for rendering. It is recommended you run `hugo` locally to validate your changes render properly.\n\n### Local Hugo Rendering\n\nHugo is available for many platforms. It can be installed using:\n\n- Linux: Most native package managers\n- macOS: `brew install hugo`\n- Windows: `choco install hugo-extended -confirm`\n\nOnce installed, you may run the following from the `/site` directory to access a rendered view of the documentation:\n\n```bash\ncd site\nhugo server --disableFastRender\n```\n\nAccess the site at [http://localhost:1313](http://localhost:1313). Press `Ctrl-C` when done viewing.\n"
  },
  {
    "path": "site/archetypes/default.md",
    "content": "---\ntitle: \"{{ replace .Name \"-\" \" \" | title }}\"\ndate: {{ .Date }}\ndraft: true\n---\n\n"
  },
  {
    "path": "site/config.yaml",
    "content": "baseURL: \"https://sealed-secrets.netlify.app\"\nlanguageCode: \"en-us\"\ntitle: \"Sealed Secrets\"\ntheme: \"template\"\noutputs:\n  home: [ \"HTML\", \"REDIRECTS\" ]\ndisableKinds:\n  - taxonomy\n  - term\npygmentsCodefences: true\npygmentsStyle: \"pygments\"\nmarkup:\n  highlight:\n    anchorLineNos: false\n    codeFences: true\n    guessSyntax: false\n    hl_Lines: \"\"\n    lineAnchors: \"\"\n    lineNoStart: 1\n    lineNos: false\n    lineNumbersInTable: true\n    noClasses: true\n    style: native\n    tabWidth: 4\nmenu:\n  docs:\n  - name: Overview\n    url: /docs/\n    weight: 100\n  - name: Architecture\n    url: /docs/architecture/\n    name: Demo\n    url: /docs/demo/\n  - name: Scope\n    url: /docs/scope/\n  - name: Update Images\n    url: /docs/contributing/\nparams:\n  twitter_url: \"https://twitter.com/bitnami\"\n  github_url: \"https://github.com/bitnami-labs/sealed-secrets\"\n  slack_url: \"https://kubernetes.slack.com/messages/sealed-secrets\"\n  github_base_url: \"https://github.com/bitnami-labs/sealed-secrets\"\n  use_advanced_docs: true\n  docs_right_sidebar: true\n  docs_search: false\n  docs_search_index_name: index_name\n  docs_search_api_key: api_key\n  docs_versioning: true\n  docs_latest: latest\n  docs_versions:\n    - latest\nmediaTypes:\n  \"text/netlify\":\n    delimiter: \"\"\noutputFormats:\n  REDIRECTS:\n    mediaType: \"text/netlify\"\n    baseName: \"_redirects\"\n"
  },
  {
    "path": "site/content/community/_index.html",
    "content": "---\ntitle: \"Community\"\nlayout: section\n---\n\n<div class=\"hero subpage-hero\">\n    <div class=\"wrapper\">\n        <h1>Community</h1>\n    </div>\n</div>\n<div class=\"wrapper subpage\">\n    <h2>Do you want to help us build Sealed Secrets?</h2>\n    <div class=\"grid two\">\n        <div class=\"col\">\n            <div class=\"icon\">\n                <img src=\"/img/github-image.svg\" />\n            </div>\n            <div class=\"content\">\n                <h3><a href=\"https://github.com/bitnami-labs/sealed-secrets\">Check out GitHub</a></h3>\n                    <p>\n                        You can follow the work we do, be part of on-going discussions, and\n                        examine our improvement ideas on the\n                        <a\n                          href=\"https://github.com/bitnami-labs/sealed-secrets/projects/1\"\n                          target=\"_blank\"\n                          >GitHub project page</a\n                        >.\n                      </p>\n                      <p>\n                        If you are a newcomer, check out the\n                        <code>good first issue</code> label\n                        <a\n                          href=\"https://github.com/bitnami-labs/sealed-secrets/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22\"\n                          target=\"_blank\"\n                          >in the repository</a\n                        >.\n                      </p>\n                      <p>\n                        If you are ready to jump in and add code, tests, or help with\n                        documentation, follow the guidelines in the\n                        <a href=\"/docs/latest/project/contributing/\"\n                          >contributing documentation</a\n                        >.\n                      </p>\n            </div>\n        </div>\n        <div class=\"col\">\n            <div class=\"icon\">\n                <img src=\"/img/slack.svg\" />\n            </div>\n            <div class=\"content\">\n                <h3><a href=\"https://kubernetes.slack.com/messages/sealed-secrets\">Join our Slack channel</a></h3>\n                <p>\n                    Join the\n                    <a\n                      target=\"_blank\"\n                      rel=\"noopener\"\n                      href=\"https://kubernetes.slack.com/messages/sealed-secrets\"\n                      >#sealed-secrets channel</a\n                    >\n                    on the Kubernetes Slack and talk to us and over 600 other community\n                    members.\n                  </p>\n                  <p>\n                    If you aren't already a member on the Kubernetes Slack workspace,\n                    please\n                    <a target=\"_blank\" rel=\"noopener\" href=\"https://slack.k8s.io\"\n                      >request an invitation</a\n                    >.\n                  </p>\n                  <p>\n                    We love discussing various Kubernetes Security workflows, patterns, helping\n                    with design and issues.\n                  </p>\n            </div>\n        </div>\n    </div>\n</div>"
  },
  {
    "path": "site/content/contributors/agarcia-oss.md",
    "content": "---\nfirst_name: Alfredo\nlast_name: García\nimage: /img/team/agarcia-oss.png\ngithub_handle: agarcia-oss\n---\nMaintainer"
  },
  {
    "path": "site/content/contributors/alvneiayu.md",
    "content": "---\nfirst_name: Alvaro\nlast_name: Neira\nimage: /img/team/alvneiayu.png\ngithub_handle: alvneiayu\n---\nMaintainer"
  },
  {
    "path": "site/content/contributors/index.md",
    "content": "---\nheadless: true\n---"
  },
  {
    "path": "site/content/docs/CONTRIBUTING.md",
    "content": "# Contributing\n\nLeo urna molestie at elementum eu facilisis sed odio. Non nisi est sit amet facilisis.\n\n## Magna etiam tempor orci \n\nCongue eu consequat [scope](../scope/) ac felis donec et odio.\n\nElit eget gravida cum sociis. Tempus quam pellentesque nec nam aliquam. \nDolor purus non enim praesent elementum facilisis leo.\n\n## Cras semper auctor neque vitae tempus\n\nQuis eleifend quam adipiscing vitae proin. Mauris pellentesque pulvinar \npellentesque habitant morbi tristique senectus et.\n\n## Ultrices vitae auctor eu augue ut lectus arcu bibendum.\n\nUt aliquam purus sit amet. Id ornare arcu odio ut sem nulla pharetra. \nJusto nec ultrices dui sapien eget mi proin sed. \nNulla malesuada pellentesque elit eget gravida.\n\n### Imperdiet sed euismod\n\n```bash\n nisi porta\n```\n\n### Egestas pretium aenean\n\n```bash\n pharetr/magna/ac/placerat/vestibulum\n```\n\n### Vitae proin sagittis nisl \n\n1. Rhoncus mattis rhoncus: \n\n   - Ante in nibh mauris cursus mattis.\n   - Elementum eu facilisis sed odio morbi quis commodo odio.\n   - Id faucibus nisl tincidunt eget nullam non nisi est sit.\n   - In fermentum posuere urna nec.\n   - Interdum velit laoreet id donec ultrices tincidunt arcu non sodales.\n\n\n   ```bash\n   At urna condimentum mattis pellentesque id. Amet venenatis urna cursus eget.\n   ```\n\n1.  Ut tortor pretium viverra suspendisse `potenti`:\n\n   ```bash\n   Ut tortor pretium viverra suspendisse potenti.\n   ```\n\n1. Arcu dui vivamus arcu felis bibendum ut `tristique et`:\n\n   ```bash\n   Ut tellus elementum sagittis vitae et leo duis ut. Urna nunc id cursus metus aliquam.\n   ```\n\n   In metus vulputate eu scelerisque felis imperdiet proin fermentum leo.\n\nSuscipit adipiscing bibendum est ultricies `integer quis`. \nCras pulvinar mattis nunc sed blandit nisl pretium `fusce id velit`.\n\n\n1. Porta non pulvinar neque laoreet suspendisse interdum consectetur.:\n\n   ```bash\n   Senectus et netus et malesuada fames ac turpis egestas.\n   ```\n\nLeo urna molestie at elementum eu facilisis sed odio. Non nisi est sit amet facilisis magna etiam tempor orci."
  },
  {
    "path": "site/content/docs/_index.md",
    "content": "---\nversion: latest\ncascade:\n  layout: docs\n---\n\n# Sealed Secrets documentation\n\nExplore the [latest version docs](./latest/) to get started.\n"
  },
  {
    "path": "site/content/docs/img/_index.md",
    "content": "# Update Images\n\n## Imperdiet sed euismod nisi porta\n\n- [Image](placeholder-750x250.png) gestas pretium aenean `plantuml`.\n\n- [Image](placeholder-750x250.png) gestas pretium aenean `vestibulum`.\n\n- Vitae proin sagittis nisl rhoncus mattis rhoncus. Ante in nibh mauris cursus mattis. \nElementum eu facilisis sed odio morbi quis commodo odio. Id faucibus nisl tincidunt eget nullam non nisi est sit. \nIn fermentum posuere urna nec. Interdum velit laoreet id donec ultrices tincidunt arcu non sodales. \nAt urna condimentum mattis pellentesque id. Amet venenatis urna cursus eget."
  },
  {
    "path": "site/content/docs/latest/README.md",
    "content": "\n# Sealed Secrets documentation\n\nEverything you need to know about Sealed Secrets.\n\n> NOTE: we are currently moving our docs to a new website and reviewing our documentation files. During the process, eventual broken links and minor inconsistences may appear. Please, feel free to contact us if you have any questions: file an [issue](https://github.com/bitnami-labs/sealed-secrets/issues), or talk to us on the [#Sealed Secrets slack channel](https://kubernetes.slack.com/messages/sealed-secrets).\n\n## Documentation overview\n\nA high-level overview of how it's organized will help Sealed Secrets contributors and users know where to look for certain things.\n\n| Section                     | Description                                                                                         |\n| --------------------------- | ----------------------------------------------------------------------------------------------------|\n| [Tutorials](./tutorials/)   | Start here if you're new to Sealed Secrets. A hands-on introduction to Seled Secrets for new users. |\n| [How-to guides](./howto/)   | They guide you through the steps involved in addressing key problems and use-cases.                 |\n| [Background](./background/) | Dive into the overall architecture and implementation details of Sealed Secrets.                    |\n| [Reference](./reference/)   | Technical information - developer guides, design proposals, examples, translations, etc.            |\n"
  },
  {
    "path": "site/content/docs/latest/_index.md",
    "content": "---\nversion: latest\ncascade:\n  layout: docs\n---\n\n{{%  readfile file=\"/content/docs/latest/README.md\" %}}\n"
  },
  {
    "path": "site/content/docs/latest/background/README.md",
    "content": "# Sealed Secrets Background\n\nBig-picture explanations of higher-level Sealed Secrets concepts. Most useful for building understanding of a particular topic.\n\n| Background                                         | Description                                                                                      |\n| -------------------------------------------------- | ------------------------------------------------------------------------------------------------ |\n| [Cryptography](./cryptography.md)                  | Dive into the overall Sealed Secrets Cryptography.                                                     |\n\nAlternatively, if you have a specific goal, but are already familiar with Sealed Secrets, take a look at our [How-to guides](../howto/README.md). These have more in-depth detail and can be applied to a broader set of features.\n\nTake a look at our [Reference section](../reference/README.md) when you need to know design decisions, detailed developer guides, etc.\n\nFinally, our [Tutorials section](../tutorials/README.md) contains step-by-step tutorials to help outline what Sealed Secrets is capable of.\n"
  },
  {
    "path": "site/content/docs/latest/background/_index.md",
    "content": "---\nversion: latest\ncascade:\n  layout: docs\n---\n\n{{%  readfile file=\"/content/docs/latest/background/README.md\" %}}\n"
  },
  {
    "path": "site/content/docs/latest/background/cryptography.md",
    "content": "# Cryptography details of Sealed Secrets\n\n<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->\n\n  - [Protocols and cryptographic tools used](#protocols-and-cryptographic-tools-used)\n  - [Entropy considerations](#entropy-considerations)\n  - [Sealing process](#sealing-process)\n    - [Public/private key pair management](#publicprivate-key-pair-management)\n    - [Secret encryption](#secret-encryption)\n    - [Session key encryption](#session-key-encryption)\n    - [Sealed Secret storage](#sealed-secret-storage)\n    - [Diagram to summarize](#diagram-to-summarize)\n    - [Decryption process](#decryption-process)\n- [Post-quantum cryptography considerations](#post-quantum-cryptography-considerations)\n  - [Entropy source](#entropy-source)\n    - [Analysis](#analysis)\n    - [Associated documentation](#associated-documentation)\n  - [AES-256-GCM](#aes-256-gcm)\n    - [Analysis](#analysis-1)\n    - [Recommendations](#recommendations)\n    - [Associated documentation](#associated-documentation-1)\n  - [SHA-256](#sha-256)\n    - [Analysis](#analysis-2)\n    - [Recommendations](#recommendations-1)\n    - [Associated documentation](#associated-documentation-2)\n  - [RSA-OAEP](#rsa-oaep)\n    - [Analysis](#analysis-3)\n    - [Recommendations](#recommendations-2)\n    - [Associated documentation](#associated-documentation-3)\n\n<!-- END doctoc generated TOC please keep comment here to allow auto update -->\n\n## Protocols and cryptographic tools used\n\nSealed Secrets uses the following protocols for the secret management:\n\n- **AES-256-GCM** with a randomly generated single-use 32 bytes session key. Since the key is single-use, we do not use any nonce. The key is used to encrypt the secret, ensuring its confidentiality and integrity.\n- **RSA-OAEP**, with **SHA-256**. It is used to assure the confidentiality of the AES-256-GCM session key, following the *key encapsulation mechanism*.\n- **X509** certificates are used to manage RSA public keys. This public key contained in the certificate can be used to encrypt AES-256-GCM session key.\n\nCertificates generated by the sealed secrets controller are renewed every 30 days and have a validity span of 10 years.\n\n## Entropy considerations\n\nThe golang API used for the entropy is `crypto/rand`. The following description can be found about the entropy generator used regarding the host system:\n\n```\n// On Linux, FreeBSD, Dragonfly and Solaris, Reader uses getrandom(2) if\n// available, /dev/urandom otherwise.\n// On OpenBSD and macOS, Reader uses getentropy(2).\n// On other Unix-like systems, Reader reads from /dev/urandom.\n// On Windows systems, Reader uses the RtlGenRandom API.\n// On Wasm, Reader uses the Web Crypto API.\n```\n\nThose cryptographic APIs are known to provide a good cryptographic entropy, and are not vulnerable to cryptographic attacks unless the seed is known.\n\nFor further information about those APIs:\n\n- [Linux/FreeBSD/Dragonfly/Solaris](https://linux.die.net/man/4/urandom)\n- [OpenBSD/macOS](https://www.freebsd.org/cgi/man.cgi?query=getentropy&sektion=3&format=html)\n- [Windows](https://download.microsoft.com/download/1/c/9/1c9813b8-089c-4fef-b2ad-ad80e79403ba/Whitepaper%20-%20The%20Windows%2010%20random%20number%20generation%20infrastructure.pdf)\n- [WASM](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues)\n\n## Sealing process\n\n### Public/private key pair management\n\nThe controller looks for a cluster-wide private/public key pair on startup. If no key pair is found and none is provided manually, the controller generates a new 4096 bit (by default) RSA key pair. In both cases, the key pair is persisted in a regular Secret in the same namespace as the controller.\n\nThe public key (in the form of a self-signed certificate if it was generated by the controller) should be made publicly available to anyone wanting to use Sealed Secrets with this cluster.\n\n> Note that it is possible to use your own X509 certificate with the command bellow:\n\n```shell\nkubeseal --cert [https:/]/path/to/your-cert.pem\n```\n\nThe certificate is printed in the controller log at startup and is also available via an HTTP GET request to `/v1/cert.pem` on the controller.\n\n### Secret encryption\n\nThe secret is encrypted by AES-256-GCM with a randomly-generated single-use 32 byte session key.\n\nThe result of this operation will be called `AES encrypted data` in the next diagram, and the present step is the `1.`.\n\n### Session key encryption\n\nThe session key used by AES-256-GCM to encrypt the Secret is encapsulated with the controller's public key using RSA-OAEP with SHA256.\n\nThe OAEP input content, called `label` in the next diagram, differs depending on the sealed secret controller scope configuration. This algorithm is only used to encrypt the AES session key.\n\n- Default scope configuration : `label` is equal to the concatenation of the Secret's namespace and the Secret's name.\n- Namespace-wide scope configuration : `label` is equal to the Secret's namespace.\n- Cluster-wide scope configuration : `label` is empty.\n\nThe result of the RSA-OAEP encryption is called `RSA encrypted data` in the next diagram, and the present step is the `2.`.\n\n### Sealed Secret storage\n\nThe final Sealed Secret data format is the following (where `||` is the concatenation operator): `size of AES encrypted key (2 bytes) || RSA encrypted data || AES encrypted data`\n\n### Diagram to summarize\n\n```\n\t\t\t\t\t\t\t\tSecret\n                                                                   |\n                                                                   │\n                                                   K_s────────────►│\n                                                    │              │\n                                       K_pub───────►│              │\n                                                    │              │ 1.\n                                       label───────►│ 2.           │\n                                                    │              │\n                     ┌──────────────────────┬───────▼───────┬──────▼───────┐\nSealed Secret data = │size of AES encrypted │ RSA encrypted │ AES encrypted│\n                     │key (2 bytes)         │ data          │ data         │\n                     └──────────────────────┴───────────────┴──────────────┘\n\nK_s = 256 bits single-use session key, used by AES-GCM\nK_pub = Public key from the self-signed certificate, used by RSA-OAEP\nlabel = Additional input for RSA-OAEP encryption.\n        Content differs depending on the scope configuration:\n         * Default config : label = Secret's namespace || Secret's name\n         * Namespace-wide : label = Secret's namespace\n         * Cluster-wide : label is empty\n```\n\n### Decryption process\n\nThe decryption is simply the inversion of the encryption.\n\n`Size of AES encrypted key` is read and used to separate `RSA encrypted data` and `AES encrypted data` properly.\n\nThen the private key associated with the public key (see Session key encryption) is used with the `label` to decrypt the `RSA encrypted data`, effectively retrieving the AES session key.\n\nTo end this process, the `AES encrypted data` is decrypted using the AES session key, therefore unsealing the original Secret.\n\n# Post-quantum cryptography considerations\n\n## Entropy source\n\n### Analysis\n\nEven if QRNG (Quantum Random Number Generator) are considered better than PRNG (Pseudo Random Number Generator) in a quantum cryptography context as well as in a non-quantum context, QRNG relies on a quantum mechanical phenomenon. It requires a physical device, therefore QRNG usage is out of Sealed Secrets scope, which will stay on the `crypto/rand` usage.\n\n### Associated documentation\n\n[Combining a quantum random number generator and quantum-resistant algorithms into the GnuGPG open-source software](https://doi.org/10.1515/aot-2020-0021)\n\n## AES-256-GCM\n\n### Analysis\n\nAES-256-GCM is quantum resistant.\nGrover algorithm can reduce the bruteforce of the key from 2²⁵⁶ to 2¹²⁸ which is still considered very secure.\nNevertheless, since AES uses unchangeable 128 bits blocks, Grover algorithm can in some cases decrease the complexity of the bruteforce to 2⁶⁴.\n\n### Recommendations\n\nAES-256-GCM quantum security is not a concern.\nCases with a bruteforce complexity of 2⁶⁴ are unlikely for Sealed Secret considering how AES is used in the project.\nEven assuming that 2⁶⁴ bruteforce is likely, it can still be considered secure today (but not in the long run).\nA recommendation is to look for a AES replacement that provide 128 bits post-quantum cryptographic security in any cases, such as ChaCha20-Poly1305. Applying this recommendation is considered low priority.\n\n### Associated documentation\n\n[Quantum Security Analysis of AES](https://eprint.iacr.org/2019/272.pdf)\n\n[Critics on AES-256-GCM](https://soatok.blog/2020/05/13/why-aes-gcm-sucks/)\n\n[Security Analysis of ChaCha20-Poly1305 AEAD](https://www.cryptrec.go.jp/exreport/cryptrec-ex-2601-2016.pdf)\n\n\n## SHA-256\n\n### Analysis\n\nSHA-256 is quantum resistant.\nGrover Algorithm can reduce the bruteforce from 2²⁵⁶ to 2¹²⁸ which is considered very secure.\nIt is computationally cheaper to use a non-quantum algorithm to generate a collision than to employ a quantum computer.\n\n### Recommendations\n\nNo recommendations about SHA-256.\n\n### Associated documentation\n[Cost analysis of hash collisions: Will quantum computers make SHARCS obsolete?](https://cr.yp.to/hash/collisioncost-20090823.pdf)\n\n## RSA-OAEP\n\n### Analysis\n\nRSA-OAEP, as any RSA algorithm, **is not quantum resistant**.\nShor algorithm can be used to solve in a reasonable time 3 mathematical problems on which RSA cryptography is based on: integer factorization problem, the discrete logarithm problem and the elliptic-curve discrete logarithm problem. Therefore, RSA-OAEP is easily breakable for an attacker with quantum capability.\n\n### Recommendations\n\nReplace RSA whenever feasible. This recommendation must be the highest priority regarding the post-quantum security of Sealed Secrets.\nThere are three serious candidates to use instead of RSA: LMS and XMSS, which are Lattice-based, and McEliece with random Goppa codes, which is code-based and relies on SDP (Syndrome Decoding Problem).\nThose three algorithms are serious candidates for RSA replacement and the choice must be done carefully, without forgetting to study other algorithms such as NTRU.\n\nIt is important to qualify this recommendation with a couple of prerequisites:\n- A standard or clear recommended Public Key Cryptography Algorithm replacement emerges in the industry.\n- A reliable Go implementation is available in a compatible Open Source license.\n\nWithout such prerequisites in place, an RSA replacement cannot be commited upon.\n\n### Associated documentation\n\n[LMS](https://datatracker.ietf.org/doc/html/rfc8554)\n\n[XMSS](https://datatracker.ietf.org/doc/html/rfc8391)\n\n[Lattice-based cryptography](https://en.wikipedia.org/wiki/Lattice-based_cryptography)\n\n[McEliece](https://ipnpr.jpl.nasa.gov/progress_report2/42-44/44N.PDF)\n\n[Syndrome Decoding Problem](https://en.wikipedia.org/wiki/Decoding_methods#Syndrome_decoding)\n\n[NIST on post-quantum algorithms](https://csrc.nist.gov/Projects/post-quantum-cryptography/round-3-submissions)\n\n[Quantum-Resistant Cryptography](https://arxiv.org/ftp/arxiv/papers/2112/2112.00399.pdf)\n"
  },
  {
    "path": "site/content/docs/latest/howto/README.md",
    "content": "# How-to guides\n\nHow-to guides can be thought of as directions that guide the reader through the steps to achieve a specific end. They'll help you achieve a result but may require you to understand and adapt the steps to fit your specific requirements. Here you'll find short answers to \"How do I...?\" types of questions.\n\n| How-to-guides                                                                          | Get stuff done                                                                                                                                    |\n| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |\n| [Validate Sealed Secrets](./validate-sealed-secrets.md)                                                  | Understand how to validate an existing Sealed Secret.                                                    |\n             |\n\nAlternatively, our [Tutorials section](../tutorials/README.md) contains step-by-step tutorials to help outline what Sealed Secrets is capable of.\n\nTake a look at our [Reference section](../reference/README.md) when you need to know design decisions, detailed developer guides, etc.\n\nFinally, for a better understanding of Sealed Secrets architecture, our [Background section](../background/README.md) enables you to expand your knowledge.\n"
  },
  {
    "path": "site/content/docs/latest/howto/_index.md",
    "content": "---\nversion: latest\ncascade:\n  layout: docs\n---\n\n{{%  readfile file=\"/content/docs/latest/howto/README.md\" %}}\n"
  },
  {
    "path": "site/content/docs/latest/howto/validate-sealed-secrets.md",
    "content": "# How-to Validate existing Sealed Secrets\n\nThe `validate` Sealed Secrets feature is useful for ensuring the correctness of Sealed Secrets, especially when they need to be shared or used in various Kubernetes environments. By validating Sealed Secrets, you can verify that the encryption and decryption processes are functioning as expected and that the secrets are protected properly.\n\nIf you want to validate an existing sealed secret, `kubeseal` has the flag `--validate` to help you.\n\nGiving a file named `sealed-secrets.yaml` containing the following sealed secret:\n\n```yaml\napiVersion: bitnami.com/v1alpha1\nkind: SealedSecret\nmetadata:\n  name: mysecret\n  namespace: mynamespace\nspec:\n  encryptedData:\n    foo: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEq.....\n```\n\nYou can validate if the sealed secret was properly created or not:\n\n```console\n$ cat sealed-secrets.yaml | kubeseal --validate\n```\n\nIn case of an invalid sealed secret, `kubeseal` will show:\n\n```console\n$ cat sealed-secrets.yaml | kubeseal --validate\nerror: unable to decrypt sealed secret\n```\n"
  },
  {
    "path": "site/content/docs/latest/project/.placeholder",
    "content": "This directory is expected to contain symlinks to certain files of the root project directory. This way, these files will be rendered in Hugo.\n\nmklink readme.md ..\\..\\..\\..\\..\\README.md\nmklink code-of-conduct.md ..\\..\\..\\..\\..\\CODE_OF_CONDUCT.md\nmklink contributing.md ..\\..\\..\\..\\..\\CONTRIBUTING.md\nmklink maintainers.md ..\\..\\..\\..\\..\\MAINTAINERS.md\nmklink security.md  ..\\..\\..\\..\\..\\SECURITY.md\nmklink chart-readme.md  ..\\..\\..\\..\\..\\chart\\sealed-secrets\\README.md"
  },
  {
    "path": "site/content/docs/latest/project/_index.md",
    "content": "---\nversion: latest\ncascade:\n  layout: docs\n---\n\n{{%  readfile file=\"/content/docs/latest/project/readme.md\" %}}\n"
  },
  {
    "path": "site/content/docs/latest/reference/README.md",
    "content": "# Sealed Secrets Reference\n\nThis section contains technical reference and developer guides for Sealed Secrets.\n\n| Reference                                                         | Description                                                                                                                                     |\n| ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |\n| [FAQ](./faq.md)                                | Frequently Asked Questions.\n\nAlternatively, our [Tutorials section](../tutorials/README.md) contains step-by-step tutorials to help outline what Sealed Secrets is capable of while helping you achieve specific aims.\n\nIf you have a specific goal but are already familiar with Sealed Secrets, take a look at our [How-to guides](../howto/README.md). These have more in-depth detail and can be applied to a broader set of features.\n\nFinally, for a better understanding of Sealed Secrets architecture, our [Background section](../background/README.md) enables you to expand your knowledge.\n"
  },
  {
    "path": "site/content/docs/latest/reference/_index.md",
    "content": "---\nversion: latest\ncascade:\n  layout: docs\n---\n\n{{%  readfile file=\"/content/docs/latest/reference/README.md\" %}}\n"
  },
  {
    "path": "site/content/docs/latest/reference/faq.md",
    "content": "# Frequently Asked Questions\n\n<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->\n\n- [Will you still be able to decrypt if you no longer have access to your cluster?](#will-you-still-be-able-to-decrypt-if-you-no-longer-have-access-to-your-cluster)\n- [How can I do a backup of my SealedSecrets?](#how-can-i-do-a-backup-of-my-sealedsecrets)\n- [Can I decrypt my secrets offline with a backup key?](#can-i-decrypt-my-secrets-offline-with-a-backup-key)\n- [What flags are available for kubeseal?](#what-flags-are-available-for-kubeseal)\n- [How do I update parts of JSON/YAML/TOML/.. file encrypted with sealed secrets?](#how-do-i-update-parts-of-jsonyamltoml-file-encrypted-with-sealed-secrets)\n- [Can I bring my own (pre-generated) certificates?](#can-i-bring-my-own-pre-generated-certificates)\n- [How to use kubeseal if the controller is not running within the `kube-system` namespace?](#how-to-use-kubeseal-if-the-controller-is-not-running-within-the-kube-system-namespace)\n- [How to verify the images?](#how-to-verify-the-images)\n- [How to use one controller for a subset of namespaces](#how-to-use-one-controller-for-a-subset-of-namespaces)\n\n<!-- END doctoc generated TOC please keep comment here to allow auto update -->\n\n## Will you still be able to decrypt if you no longer have access to your cluster?\n\nNo, the private keys are only stored in the Secret managed by the controller (unless you have some other backup of your k8s objects). There are no backdoors - without that private key used to encrypt a given SealedSecrets, you can't decrypt it. If you can't get to the Secrets with the encryption keys, and you also can't get to the decrypted versions of your Secrets live in the cluster, then you will need to regenerate new passwords for everything, seal them again with a new sealing key, etc.\n\n## How can I do a backup of my SealedSecrets?\n\nIf you do want to make a backup of the encryption private keys, it's easy to do from an account with suitable access:\n\n```shell\nkubectl get secret -n kube-system -l sealedsecrets.bitnami.com/sealed-secrets-key -o yaml >main.key\n\necho \"---\" >> main.key\nkubectl get secret -n kube-system sealed-secrets-key -o yaml >>main.key\n```\n\n> NOTE: You need the second statement only if you ever installed sealed-secrets older than version 0.9.x on your cluster.\n\n> NOTE: This file will contain the controller's public + private keys and should be kept omg-safe!\n\nTo restore from a backup after some disaster, just put that secrets back before starting the controller - or if the controller was already started, replace the newly-created secrets and restart the controller:\n\n```shell\nkubectl apply -f main.key\nkubectl delete pod -n kube-system -l name=sealed-secrets-controller\n```\n\n## Can I decrypt my secrets offline with a backup key?\n\nWhile treating sealed-secrets as long term storage system for secrets is not the recommended use case, some people\ndo have a legitimate requirement for being able to recover secrets when the k8s cluster is down and restoring a backup into a new `SealedSecret` controller deployment is not practical.\n\nIf you have backed up one or more of your private keys (see previous question), you can use the `kubeseal --recovery-unseal --recovery-private-key file1.key,file2.key,...` command to decrypt a sealed secrets file.\n\n## What flags are available for kubeseal?\n\nYou can check the flags available using `kubeseal --help`.\n\n## How do I update parts of JSON/YAML/TOML/.. file encrypted with sealed secrets?\n\nA kubernetes `Secret` resource contains multiple items, basically a flat map of key/value pairs.\nSealedSecrets operate at that level, and does not care what you put in the values. In other words\nit cannot make sense of any structured configuration file you might have put in a secret and thus\ncannot help you update individual fields in it.\n\nSince this is a common problem, especially when dealing with legacy applications, we do offer an [example](docs/examples/config-template) of a possible workaround.\n\n## Can I bring my own (pre-generated) certificates?\n\nYes, you can provide the controller with your own certificates, and it will consume them.\nPlease check [here](docs/bring-your-own-certificates.md) for a workaround.\n\n## How to use kubeseal if the controller is not running within the `kube-system` namespace?\n\nIf you installed the controller in a different namespace than the default `kube-system`, you need to provide this namespace\nto the `kubeseal` command line tool. There are two options:\n\n1. You can specify the namespace via the command line option `--controller-namespace <namespace>`:\n\n  ```shell\nkubeseal --controller-namespace sealed-secrets <mysecret.json >mysealedsecret.json\n```\n\n2. Via the environment variable `SEALED_SECRETS_CONTROLLER_NAMESPACE`:\n\n  ```shell\nexport SEALED_SECRETS_CONTROLLER_NAMESPACE=sealed-secrets\nkubeseal <mysecret.json >mysealedsecret.json\n```\n\n## How to verify the images?\n\nOur images are being signed using [cosign](https://github.com/sigstore/cosign). The signatures have been saved in our [GitHub Container Registry](https://ghcr.io/bitnami-labs/sealed-secrets-controller/signs).\n\n> Images up to and including v0.20.2 were signed using Cosign v1. Newer images are signed with Cosign v2.\n\nIt is pretty simple to verify the images:\n\n```console\n# export the COSIGN_VARIABLE setting up the GitHub container registry signs path\n$ export COSIGN_REPOSITORY=ghcr.io/bitnami-labs/sealed-secrets-controller/signs\n\n# verify the image uploaded in GHCR\n$ cosign verify --key .github/workflows/cosign.pub ghcr.io/bitnami-labs/sealed-secrets-controller:latest\n\nVerification for ghcr.io/bitnami-labs/sealed-secrets-controller:latest --\nThe following checks were performed on each of these signatures:\n  - The cosign claims were validated\n  - Existence of the claims in the transparency log was verified offline\n  - The signatures were verified against the specified public key\n...\n\n# verify the image uploaded in Dockerhub\n$ cosign verify --key .github/workflows/cosign.pub docker.io/bitnami/sealed-secrets-controller:latest\n\nVerification for index.docker.io/bitnami/sealed-secrets-controller:latest --\nThe following checks were performed on each of these signatures:\n  - The cosign claims were validated\n  - Existence of the claims in the transparency log was verified offline\n  - The signatures were verified against the specified public key\n...\n```\n\n## How to use one controller for a subset of namespaces\n\nIf you want to use one controller for more than one namespace, but not all namespaces, you can provide additional namespaces using the command line flag `--additional-namespaces=<namespace1>,<namespace2>,<...>`. Make sure you provide appropriate roles and rolebindings in the target namespaces, so the controller can manage the secrets in there.\n"
  },
  {
    "path": "site/content/docs/latest/tutorials/README.md",
    "content": "# Sealed Secrets tutorials\n\nThis section of our documentation contains step-by-step tutorials to help outline what Sealed Secrets is capable of.\n\nWe hope our tutorials make as few assumptions as possible and are broadly accessible to anyone with an interest in Sealed Secrets. They should also be a good place to start learning about Sealed Secrets, how it works and what it's capable of.\n\n| Tutorial                                | Description                                                                                                                  |\n|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------|\n| [Getting started](./getting-started.md) | This guide walks you through the process of deploying Sealed Secrets for your cluster and installing an example Sealed Secrets. |\n| [Sealed Secrets controller installation](./install-sealed-secrets.md) | Here we cover the different alternatives to install the Sealed Secrets controller, with special notes for environments with restricted permissions. |\n\nAlternatively, if you have a specific goal, but are already familiar with Sealed Secrets, take a look at our [How-to guides](../howto/README.md). These have more in-depth detail and can be applied to a broader set of features.\n\nTake a look at our [Reference section](../reference/README.md) when you need to know design decisions, detailed developer guides, etc.\n\nFinally, for a better understanding of Sealed Secrets architecture, our [Background section](../background/README.md) enables you to expand your knowledge.\n"
  },
  {
    "path": "site/content/docs/latest/tutorials/_index.md",
    "content": "---\nversion: latest\ncascade:\n  layout: docs\n---\n\n{{%  readfile file=\"/content/docs/latest/tutorials/README.md\" %}}\n"
  },
  {
    "path": "site/content/docs/latest/tutorials/getting-started.md",
    "content": "# Get Started with Sealed Secrets\n\n## Table of Contents\n\n1. [Introduction](#introduction)\n1. [Pre-requisites](#pre-requisites)\n1. [Step 1: Install the Sealed Secrets](#step-1-install-sealed-secrets)\n1. [Step 2: Encrypt local secrets into Sealed Secrets](#step-2-encrypt-local-secrets-into-sealed-secrets)\n\n## Introduction\n\n[Sealed Secrets](https://github.com/bitnami-labs/sealed-secrets) is commonly used for achieving declarative Kubernetes Secret Management. The project offers a mechanism to encrypt secrets locally. Since the Sealed Secrets are encrypted, they can be safely stored in a code repository. This enables an easy to implement GitOps flow that is very popular among the OSS community.\n\nThis guide walks you through the process of deploying Sealed Secrets in your cluster and installing an example secret.\n\n## Pre-requisites\n\n- Sealed Secrets assumes a working Kubernetes cluster (v1.16+), as well as the [`helm`](https://helm.sh/docs/intro/install/) (v3.1.0+) and [`kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/) command-line interfaces installed and configured to talk to your Kubernetes cluster.\n\n- Sealed Secrets has been tested with Amazon Elastic Kubernetes Service (EKS) Azure Kubernetes Service (AKS), Google Kubernetes Engine (GKE), minikube and Openshift.\n\n## Step 1: Install Sealed Secrets\n\nSealed Secrets is composed of two parts:\n\n- A cluster-side controller\n- A client-side utility: kubeseal\n\n### Installing the sealed-secrets-controller\n\nThe controller can be deployed using three different methods: direct yaml manifest installation, helm chart or carvel package.\n\n#### Sealed Secrets manifest\n\nSealed secrets controller manifests are available from the [releases page](https://github.com/bitnami-labs/sealed-secrets/releases). You can choose the most convenient deployment for your cluster:\n\n- `controller.yaml` Is a full manifest description of all the components required for the Sealed Secrets controller to operate. This includes Cluster role permissions and CRD definitions.\n- `controller-norbac.yaml` Is a restricted version of the manifest descriptor. This version does not include CRDs nor Cluster roles.\n\n#### Helm chart\n\nThe Sealed Secrets [Helm chart](https://helm.sh/) is officially supported and hosted in this GitHub repository.\n```shell\nhelm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets\nhelm install sealed-secrets-controller sealed-secrets/sealed-secrets \\\n--set namespace=kube-system \\\n```\n\n> The kubeseal CLI assumes that the controller is installed within the `kube-system` namespace by default with a deployment named `sealed-secrets-controller`. The above installation defines the same configuration to avoid unnecessary friction while using kubeseal.\n\n#### Carvel package\n\nIt is also possible to install Sealed Secrets as a [Carvel package](https://carvel.dev/kapp-controller/docs/v0.46.0/packaging/). To do so, you'll need to install `kapp-controller` in the target cluster and then deploy the needed `Package` and `PackageInstall` manifests.\n\n```shell\n# Deploy kapp-controller\nkapp deploy -a kc -f https://github.com/vmware-tanzu/carvel-kapp-controller/releases/latest/download/release.yml\n# Deploy the Sealed Secrets package in the cluster\nkapp deploy -a sealed-secrets-carvel -f https://raw.githubusercontent.com/bitnami-labs/sealed-secrets/main/carvel/package.yaml\nChanges\n\nNamespace  Name                              Kind     Conds.  Age  Op      Op st.  Wait to    Rs  Ri\ndefault    sealedsecrets.bitnami.com.2.10.0  Package  -       -    create  -       reconcile  -   -\n...\nSucceeded\n\nkubectl get Package\nNAME                               PACKAGEMETADATA NAME        VERSION   AGE\nsealedsecrets.bitnami.com.2.10.0   sealedsecrets.bitnami.com   2.10.0    18s\n```\n\nOnce the Package is available, it'll be necessary to execute the PackageInstall action, following the [carvel documentation](https://carvel.dev/kapp-controller/docs/v0.35.0/packaging-tutorial/#installing-a-package).\n\n### Installing the kubeseal CLI\n\n#### Homebrew\n\nThe `kubeseal` client is available on [homebrew](https://formulae.brew.sh/formula/kubeseal):\n\n```bash\nbrew install kubeseal\n```\n\n#### MacPorts\n\nThe `kubeseal` client is also available on [MacPorts](https://ports.macports.org/port/kubeseal/summary):\n\n```bash\nport install kubeseal\n```\n\n#### Nixpkgs\n\nThe `kubeseal` client is also available on [Nixpkgs](https://search.nixos.org/packages?channel=unstable&show=kubeseal&from=0&size=50&sort=relevance&type=packages&query=kubeseal): (**DISCLAIMER**: Not maintained by sealed secrets).\n\n```bash\nnix-env -iA nixpkgs.kubeseal\n```\n\n#### Linux\n\nThe `kubeseal` client can be installed on Linux, using the below commands:\n\n```bash\nwget https://github.com/bitnami-labs/sealed-secrets/releases/download/<release-tag>/kubeseal-<version>-linux-amd64.tar.gz\ntar -xvzf kubeseal-<version>-linux-amd64.tar.gz kubeseal\nsudo install -m 755 kubeseal /usr/local/bin/kubeseal\n```\n\nwhere `release-tag` is the [version tag](https://github.com/bitnami-labs/sealed-secrets/tags) of the kubeseal release you want to use. For example: `v0.21.0`.\n\n#### Installation from source\n\nIf you just want the latest client tool, it can be installed into\n`$GOPATH/bin` with:\n\n```bash\ngo install github.com/bitnami-labs/sealed-secrets/cmd/kubeseal@main\n```\n\nYou can specify a release tag or a commit SHA instead of `main`.\n\nThe `go install` command will place the `kubeseal` binary at `$GOPATH/bin`:\n\n```bash\n$(go env GOPATH)/bin/kubeseal\n```\n\n## Step 2: Encrypt local secrets into Sealed Secrets\n\n```bash\n# Create a json/yaml-encoded Secret somehow:\n# (note use of `--dry-run` - this is just a local file!)\necho -n bar | kubectl create secret generic mysecret --dry-run=client --from-file=foo=/dev/stdin -o json >mysecret.json\n\n# This is the important bit:\nkubeseal -f mysecret.json -w mysealedsecret.json\n\n# At this point mysealedsecret.json is safe to upload to Github,\n# post on Twitter, etc.\n\n# Eventually:\nkubectl create -f mysealedsecret.json\n\n# Profit!\nkubectl get secret mysecret\n```\n\n> The `SealedSecret` and `Secret` must have **the same namespace and\nname**. This is a feature to prevent other users on the same cluster\nfrom re-using your sealed secrets.\n"
  },
  {
    "path": "site/content/docs/latest/tutorials/install-sealed-secrets.md",
    "content": "# Sealed Secrets controller installation\n\n<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->\n\n- [Assumptions and prerequisites](#assumptions-and-prerequisites)\n- [Installing from Manifests](#installing-from-manifests)\n  - [Installing in a GKE cluster](#installing-in-a-gke-cluster)\n- [Installing the Helm Chart](#installing-the-helm-chart)\n  - [Installing in an Openshift cluster](#installing-in-an-openshift-cluster)\n- [Installing the Carvel package](#installing-the-carvel-package)\n\n<!-- END doctoc generated TOC please keep comment here to allow auto update -->\n\n## Assumptions and prerequisites\n\n- You have access to an existing Kubernetes cluster (v1.16+).\n- You have [`kubectl`](https://kubernetes.io/docs/tasks/tools/) command-line interface installed and configured to talk to your Kubernetes cluster.\n- For the Helm installation, you have the [`helm`](https://helm.sh/docs/intro/install/) (v3.1.0+) command-line interface installed and configured to talk to your Kubernetes cluster.\n- For the Carvel installation, you have the [`kapp`](https://carvel.dev/kapp/docs/latest/install/) command-line interface installed and configured to talk to your Kubernetes cluster.\n\nThe controller can be deployed using three different methods: direct yaml manifest installation, helm chart or carvel package.\n\n## Installing from Manifests\n\nSealed secrets controller manifests are available from the [releases page](https://github.com/bitnami-labs/sealed-secrets/releases). You can choose the most convenient deployment for your cluster:\n\n- `controller.yaml` Is a full manifest description of all the components required for the Sealed Secrets controller to operate. This includes Cluster role permissions and CRD definitions.\n- `controller-norbac.yaml` Is a restricted version of the manifest descriptor. This version does not include CRDs nor Cluster roles.\n\nTo install the controller simply type:\n\n```console\n$ kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/{{VERSION}}/controller.yaml\n\nrole.rbac.authorization.k8s.io/sealed-secrets-service-proxier created\nrolebinding.rbac.authorization.k8s.io/sealed-secrets-controller created\nclusterrolebinding.rbac.authorization.k8s.io/sealed-secrets-controller created\nserviceaccount/sealed-secrets-controller created\ndeployment.apps/sealed-secrets-controller created\ncustomresourcedefinition.apiextensions.k8s.io/sealedsecrets.bitnami.com configured\nrolebinding.rbac.authorization.k8s.io/sealed-secrets-service-proxier created\nservice/sealed-secrets-controller created\nrole.rbac.authorization.k8s.io/sealed-secrets-key-admin created\nclusterrole.rbac.authorization.k8s.io/secrets-unsealer configured\n```\n\nWhere `{{VERSION}}` is the Sealed Secrets latest version (i.e `v0.22.0`).\n\nOnce you deploy the manifest it will create the SealedSecret resource and install the controller into `kube-system` namespace, create a service account and necessary RBAC roles.\n\nAfter a few moments, the controller will start, generate a key pair, and be ready for operation. If it does not, check the controller logs.\n\n### Installing in a GKE cluster\n\nInstalling the controller on GKE clusters without admin rights might be problematic. For that, a `ClusterRoleBinding` will be needed to deploy the controller in the final command.  Replace `{{your-email}}` with a valid email, and then deploy the cluster role binding:\n\n```bash\nUSER_EMAIL={{your-email}}\nkubectl create clusterrolebinding $USER-cluster-admin-binding --clusterrole=cluster-admin --user=$USER_EMAIL\n```\n\nPlease refer to the [GKE how-to](../howto/) for additional instructions on that platform.\n\n## Installing the Helm Chart\n\nThe Sealed Secrets [Helm chart](https://helm.sh/) is officially supported and hosted in this GitHub repository.\n```shell\nhelm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets\nhelm install sealed-secrets-controller sealed-secrets/sealed-secrets \\\n--set namespace=kube-system \\\n```\n\n> The `kubeseal` CLI assumes that the controller is installed within the `kube-system` namespace by default with a deployment named `sealed-secrets-controller`. The above installation defines the same configuration to avoid unnecessary friction while using kubeseal.\n\n### Installing in an Openshift cluster\n\nOpenshift installations will require some minor adjustments to comply with the standard Container Security Context restrictions:\n\n```yaml\ncontainerSecurityContext:\n  enabled: true\n  readOnlyRootFilesystem: true\n  runAsNonRoot: true\n  runAsUser: null\npodSecurityContext:\n```\n\n## Installing the Carvel package\n\nIt is also possible to install Sealed Secrets as a [Carvel package](https://carvel.dev/kapp-controller/docs/v0.46.0/packaging/). To do so, you'll need to install `kapp-controller` in the target cluster and then deploy the needed `Package` and `PackageInstall` manifests.\n\n```console\n$ kapp deploy -a kc -f https://github.com/vmware-tanzu/carvel-kapp-controller/releases/latest/download/release.yml\n\n$ kapp deploy -a sealed-secrets-carvel -f https://raw.githubusercontent.com/bitnami-labs/sealed-secrets/main/carvel/package.yaml\nChanges\n\nNamespace  Name                              Kind     Conds.  Age  Op      Op st.  Wait to    Rs  Ri\ndefault    sealedsecrets.bitnami.com.2.10.0  Package  -       -    create  -       reconcile  -   -\n...\nSucceeded\n\n$ kubectl get Package\nNAME                               PACKAGEMETADATA NAME        VERSION   AGE\nsealedsecrets.bitnami.com.2.10.0   sealedsecrets.bitnami.com   2.10.0    18s\n```\n\nOnce the Package is available, it'll be necessary to execute the PackageInstall action, following the [carvel documentation](https://carvel.dev/kapp-controller/docs/v0.35.0/packaging-tutorial/#installing-a-package).\n"
  },
  {
    "path": "site/content/posts/_index.md",
    "content": "---\ntitle: \"Blog\"\nid: blog\nurl: /blog\noutputs: [\"HTML\", \"RSS\"]\nlayout: listß\n_build:\n  render: never\n  list: never\n---\n\n"
  },
  {
    "path": "site/content/resources/_index.html",
    "content": "---\nlayout: page\ntitle: Resources\ndescription: Sealed Secrets Resources\nid: resources\n---\n\n<div class=\"hero subpage-hero\">\n    <div class=\"wrapper\">\n        <h1>Resources</h1>\n    </div>\n</div>\n<div class=\"subpage wrapper resources\">\n    <h2>Some useful external resources about Sealed Secrets, such as videos, workshops, and community articles.</h2>\n    <div class=\"grid three\">\n        <div class=\"col\">\n            <div class=\"icon\">\n                <img src=\"/img/youtube.svg\" style=\"width: 90%;\" />\n            </div>\n            <div class=\"content\">\n                <p><a href=\"https://www.youtube.com/watch?v=x-cDk8DIvwE&t=1397s\">TGI Kubernetes 132 (with Joe Beda).</a>\n                </p>\n            </div>\n        </div>\n        <div class=\"col\">\n            <div class=\"icon\">\n                <img src=\"/img/youtube.svg\" style=\"width: 90%;\" />\n            </div>\n            <div class=\"content\">\n                <p><a href=\"https://www.youtube.com/watch?v=kYn8WN5UDiM\">Angus Lees Kubernetes Sealed Secrets</a></p>\n            </div>\n        </div>\n        <div class=\"col\">\n            <div class=\"icon\">\n                <img src=\"/img/youtube.svg\" style=\"width: 90%;\" />\n            </div>\n            <div class=\"content\">\n                <p><a href=\"https://www.youtube.com/watch?v=FJBmovA2Ej4\">GitOps Secrets with ArgoCD</a></p>\n            </div>\n        </div>\n        <div class=\"col\">\n            <div class=\"icon\">\n                <img src=\"/img/sealedsecrets-logo.svg\" />\n            </div>\n            <div class=\"content\">\n                <p class=\"strong\">Sealed Secrets: Protecting your passwords before they reach Kubernetes</p>\n                <p><a\n                        href=\"https://docs.bitnami.com/tutorials/sealed-secrets\">https://docs.bitnami.com/tutorials/sealed-secrets</a>\n                </p>\n            </div>\n        </div>\n        <div class=\"col\">\n            <div class=\"icon\">\n                <img src=\"/img/vmware-broadcom-logo.svg\" style=\"width: 90%;\" />\n            </div>\n            <div class=\"content\">\n                <p class=\"strong\">Tanzu Development Center: Secret Management</p>\n                <p><a\n                        href=\"https://tanzu.vmware.com/developer/guides/platform-security-secret-management/#sealed-secrets\">https://tanzu.vmware.com/developer/</a>\n                </p>\n            </div>\n        </div>\n        <div class=\"col\">\n            <div class=\"icon\">\n                <img src=\"/img/sealedsecrets-logo.svg\" />\n            </div>\n            <div class=\"content\">\n                <p class=\"strong\">FluxCd configuration with Sealed Secrets</p>\n                <p><a\n                        href=\"https://fluxcd.io/docs/guides/sealed-secrets/\">https://fluxcd.io/docs/guides/sealed-secrets/</a>\n                </p>\n            </div>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "site/data/docs/latest-toc.yml",
    "content": "---\ntoc:\n  - title: About Sealed Secrets\n    subfolderitems:\n      - page: What is Sealed Secrets\n        url: /project/readme\n      - page: Sealed Secrets helm chart\n        url: /project/chart-readme\n\n  - title: Tutorials\n    subfolderitems:\n      - url: /tutorials/getting-started\n        page: Get Started with Sealed Secrets\n      - url: /tutorials/install-sealed-secrets\n        page: Sealed Secrets controller installation\n  - title: How-to guides\n    subfolderitems:\n      - url: /howto/validate-sealed-secrets\n        page: Validate an existing Sealed Secret\n\n  - title: Background\n    subfolderitems:\n      - url: /background/cryptography\n        page: Cryptography details\n\n  - title: Reference\n    subfolderitems:\n      - url: /reference/faq\n        page: FAQ\n"
  },
  {
    "path": "site/data/docs/toc-mapping.yml",
    "content": "# This file can be used to explicitly map a release to a specific table-of-contents\n# (TOC). You'll want to use this after any revamps to information architecture, to ensure\n# that the navigation for older versions still work.\n\nlatest: latest-toc"
  },
  {
    "path": "site/resources/_gen/assets/scss/scss/site.scss_8967e03afb92eb0cac064520bf021ba2.content",
    "content": "body{font-family:\"Metropolis-Light\",Helvetica,sans-serif;margin:0px;line-height:1.25}.wrapper{max-width:980px;margin:0px auto;padding:20px}@media only screen and (max-width: 767px){.wrapper{max-width:100%}}@media only screen and (min-width: 1440px){.wrapper.docs{max-width:80%}}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:\"\";line-height:0}.clearfix:after{clear:both}h1,h2,h3,h4,h5,h6{font-weight:300}h1{font-size:28px}h2{font-size:22px;color:#333}h3{font-size:20px}h4{font-size:18px}li{list-style-type:none;display:inline;padding-right:25px;font-size:14px;line-height:1.7em}li:last-of-type{padding-right:0px}p{line-height:1.7em;font-weight:300;font-size:16px;color:#333}p.intro{font-size:18px}a{font-size:16px;text-decoration:none;color:#0095D3;font-family:\"Metropolis-Medium\",Helvetica,sans-serif}button{background-color:unset;border:none}.button{color:#0095D3;font-size:12px;font-weight:600;background-color:#fff;border-radius:3px;padding:14px 10px;min-width:200px;text-transform:uppercase;border:1px solid #fff}.button.secondary{background-color:#0091DA;color:#fff}.button.tertiary{border:1px solid #0095D3}.buttons{margin-top:40px}.buttons .button:first-of-type{margin-right:30px}@media only screen and (max-width: 767px){.buttons .button:first-of-type{margin:0px 0px 20px 0px}}.strong{font-family:\"Metropolis-Medium\",Helvetica,sans-serif}.bg-grey{background-color:#F2F2F2}.grid.three{display:grid;grid-template-columns:1fr 1fr 1fr;row-gap:20px;column-gap:20px}@media only screen and (max-width: 767px){.grid.three{grid-template-columns:1fr}}.grid.two{display:grid;grid-template-columns:1fr 1fr}@media only screen and (max-width: 767px){.grid.two{grid-template-columns:1fr}}.rounded-circle{border-radius:50% !important}noscript{background-color:rgba(255,0,0,0.25);display:block;font-size:14px;font-weight:bold;padding:10px;margin:0 20px 20px 0}.filter-blue{filter:brightness(0) saturate(100%) invert(39%) sepia(55%) saturate(4454%) hue-rotate(177deg) brightness(99%) contrast(104%)}@font-face{font-family:\"Metropolis-Bold\";src:url(\"/fonts/Metropolis-Bold.eot\");src:url(\"/fonts/Metropolis-Bold.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-Bold.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-Bold.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-BoldItalic\";src:url(\"/fonts/Metropolis-BoldItalic.eot\");src:url(\"/fonts/Metropolis-BoldItalic.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-BoldItalic.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-BoldItalic.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-Light\";src:url(\"/fonts/Metropolis-Light.eot\");src:url(\"/fonts/Metropolis-Light.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-Light.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-Light.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-LightItalic\";src:url(\"/fonts/Metropolis-LightItalic.eot\");src:url(\"/fonts/Metropolis-LightItalic.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-LightItalic.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-LightItalic.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-Regular\";src:url(\"/fonts/Metropolis-Regular.eot\");src:url(\"/fonts/Metropolis-Regular.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-Regular.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-Regular.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-RegularItalic\";src:url(\"/fonts/Metropolis-RegularItalic.eot\");src:url(\"/fonts/Metropolis-RegularItalic.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-RegularItalic.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-RegularItalic.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-Medium\";src:url(\"/fonts/Metropolis-Medium.eot\");src:url(\"/fonts/Metropolis-Medium.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-Medium.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-Medium.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-MediumItalic\";src:url(\"/fonts/Metropolis-MediumItalic.eot\");src:url(\"/fonts/Metropolis-MediumItalic.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-MediumItalic.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-MediumItalic.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-SemiBold\";src:url(\"/fonts/Metropolis-SemiBold.eot\");src:url(\"/fonts/Metropolis-SemiBold.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-SemiBold.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-SemiBold.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-SemiBoldItalic\";src:url(\"/fonts/Metropolis-SemiBoldItalic.eot\");src:url(\"/fonts/Metropolis-SemiBoldItalic.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-SemiBoldItalic.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-SemiBoldItalic.woff\") format(\"woff\");font-weight:normal;font-style:normal}header .wrapper{padding:10px 20px;min-height:52px;display:flex;align-items:center;justify-content:space-between}header .desktop-links{padding-left:0px}header a{color:#333;font-family:\"Metropolis-Light\",Helvetica,sans-serif}header a.active{font-family:\"Metropolis-Medium\",Helvetica,sans-serif}header li img{vertical-align:bottom;margin-right:10px}header .mobile{display:none}@media only screen and (min-width: 768px) and (max-width: 1279px){header .desktop-links li{padding-right:10px}}@media only screen and (max-width: 767px){header{position:relative}header .expanded-icon{display:none;padding:11px 3px 0px 0px}header .collapsed-icon{padding-top:12px}header .mobile-menu-visible .mobile{display:block}header .mobile-menu-visible .mobile .collapsed-icon{display:none}header .mobile-menu-visible .mobile .expanded-icon{display:block}header .desktop-links{display:none}header .mobile{display:block}header button{float:right}header button:focus{outline:none}header ul{padding-left:0px}header ul li{display:block;margin:20px 0px}header .mobile-menu{position:absolute;background-color:#fff;width:100%;top:70px;left:0px;padding-bottom:20px;display:none;z-index:10}header .mobile-menu .header-links{margin:0px 20px}header .mobile-menu .social{margin:0px 20px;padding-top:20px}header .mobile-menu .social img{vertical-align:middle;padding-right:10px}header .mobile-menu .social a{font-size:14px;padding-right:35px}header .mobile-menu .social a:last-of-type{padding-right:0px}}body{font-family:\"Metropolis-Light\",Helvetica,sans-serif;margin:0px;line-height:1.25}.wrapper{max-width:980px;margin:0px auto;padding:20px}@media only screen and (max-width: 767px){.wrapper{max-width:100%}}@media only screen and (min-width: 1440px){.wrapper.docs{max-width:80%}}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:\"\";line-height:0}.clearfix:after{clear:both}h1,h2,h3,h4,h5,h6{font-weight:300}h1{font-size:28px}h2{font-size:22px;color:#333}h3{font-size:20px}h4{font-size:18px}li{list-style-type:none;display:inline;padding-right:25px;font-size:14px;line-height:1.7em}li:last-of-type{padding-right:0px}p{line-height:1.7em;font-weight:300;font-size:16px;color:#333}p.intro{font-size:18px}a{font-size:16px;text-decoration:none;color:#0095D3;font-family:\"Metropolis-Medium\",Helvetica,sans-serif}button{background-color:unset;border:none}.button{color:#0095D3;font-size:12px;font-weight:600;background-color:#fff;border-radius:3px;padding:14px 10px;min-width:200px;text-transform:uppercase;border:1px solid #fff}.button.secondary{background-color:#0091DA;color:#fff}.button.tertiary{border:1px solid #0095D3}.buttons{margin-top:40px}.buttons .button:first-of-type{margin-right:30px}@media only screen and (max-width: 767px){.buttons .button:first-of-type{margin:0px 0px 20px 0px}}.strong{font-family:\"Metropolis-Medium\",Helvetica,sans-serif}.bg-grey{background-color:#F2F2F2}.grid.three{display:grid;grid-template-columns:1fr 1fr 1fr;row-gap:20px;column-gap:20px}@media only screen and (max-width: 767px){.grid.three{grid-template-columns:1fr}}.grid.two{display:grid;grid-template-columns:1fr 1fr}@media only screen and (max-width: 767px){.grid.two{grid-template-columns:1fr}}.rounded-circle{border-radius:50% !important}noscript{background-color:rgba(255,0,0,0.25);display:block;font-size:14px;font-weight:bold;padding:10px;margin:0 20px 20px 0}.filter-blue{filter:brightness(0) saturate(100%) invert(39%) sepia(55%) saturate(4454%) hue-rotate(177deg) brightness(99%) contrast(104%)}@font-face{font-family:\"Metropolis-Bold\";src:url(\"/fonts/Metropolis-Bold.eot\");src:url(\"/fonts/Metropolis-Bold.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-Bold.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-Bold.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-BoldItalic\";src:url(\"/fonts/Metropolis-BoldItalic.eot\");src:url(\"/fonts/Metropolis-BoldItalic.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-BoldItalic.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-BoldItalic.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-Light\";src:url(\"/fonts/Metropolis-Light.eot\");src:url(\"/fonts/Metropolis-Light.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-Light.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-Light.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-LightItalic\";src:url(\"/fonts/Metropolis-LightItalic.eot\");src:url(\"/fonts/Metropolis-LightItalic.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-LightItalic.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-LightItalic.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-Regular\";src:url(\"/fonts/Metropolis-Regular.eot\");src:url(\"/fonts/Metropolis-Regular.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-Regular.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-Regular.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-RegularItalic\";src:url(\"/fonts/Metropolis-RegularItalic.eot\");src:url(\"/fonts/Metropolis-RegularItalic.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-RegularItalic.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-RegularItalic.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-Medium\";src:url(\"/fonts/Metropolis-Medium.eot\");src:url(\"/fonts/Metropolis-Medium.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-Medium.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-Medium.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-MediumItalic\";src:url(\"/fonts/Metropolis-MediumItalic.eot\");src:url(\"/fonts/Metropolis-MediumItalic.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-MediumItalic.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-MediumItalic.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-SemiBold\";src:url(\"/fonts/Metropolis-SemiBold.eot\");src:url(\"/fonts/Metropolis-SemiBold.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-SemiBold.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-SemiBold.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-SemiBoldItalic\";src:url(\"/fonts/Metropolis-SemiBoldItalic.eot\");src:url(\"/fonts/Metropolis-SemiBoldItalic.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-SemiBoldItalic.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-SemiBoldItalic.woff\") format(\"woff\");font-weight:normal;font-style:normal}footer .top-links{min-height:52px;display:flex;align-items:center;justify-content:space-between}footer .left-links{padding:0px}footer .left-links li img{vertical-align:bottom;margin-right:10px}footer .left-links li a{color:#333;font-weight:300;font-size:12px;font-family:\"Metropolis-Light\",Helvetica,sans-serif}footer .left-links .mobile{display:none}footer .right-links p{margin:0px}footer .right-links .copywrite{font-size:12px;padding-right:10px}footer .right-links .copywrite a{font-size:12px;color:#333;font-family:\"Metropolis-Light\",Helvetica,sans-serif}footer .right-links a{vertical-align:middle}footer .bottom-links{margin:10px 0px 30px 0px;float:right}footer .bottom-links p{font-size:12px}footer .bottom-links a{font-size:12px;font-family:\"Metropolis-Light\",Helvetica,sans-serif}footer .bottom-links img{max-width:75px;vertical-align:middle;margin-left:30px}@media only screen and (max-width: 767px){footer .footer-links{display:block}footer .footer-links .right-links{display:none}footer .footer-links .left-links{float:none;margin:10px 0px}footer .footer-links .left-links .desktop{display:none}footer .footer-links .left-links .mobile{display:inline}footer .footer-links .left-links .copywrite{display:block;margin-top:20px}footer .bottom-links{margin:10px 0px 20px 0px;float:none}footer .bottom-links img{margin-left:0px;display:block;margin-top:10px}}body{font-family:\"Metropolis-Light\",Helvetica,sans-serif;margin:0px;line-height:1.25}.wrapper{max-width:980px;margin:0px auto;padding:20px}@media only screen and (max-width: 767px){.wrapper{max-width:100%}}@media only screen and (min-width: 1440px){.wrapper.docs{max-width:80%}}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:\"\";line-height:0}.clearfix:after{clear:both}h1,h2,h3,h4,h5,h6{font-weight:300}h1{font-size:28px}h2{font-size:22px;color:#333}h3{font-size:20px}h4{font-size:18px}li{list-style-type:none;display:inline;padding-right:25px;font-size:14px;line-height:1.7em}li:last-of-type{padding-right:0px}p{line-height:1.7em;font-weight:300;font-size:16px;color:#333}p.intro{font-size:18px}a{font-size:16px;text-decoration:none;color:#0095D3;font-family:\"Metropolis-Medium\",Helvetica,sans-serif}button{background-color:unset;border:none}.button{color:#0095D3;font-size:12px;font-weight:600;background-color:#fff;border-radius:3px;padding:14px 10px;min-width:200px;text-transform:uppercase;border:1px solid #fff}.button.secondary{background-color:#0091DA;color:#fff}.button.tertiary{border:1px solid #0095D3}.buttons{margin-top:40px}.buttons .button:first-of-type{margin-right:30px}@media only screen and (max-width: 767px){.buttons .button:first-of-type{margin:0px 0px 20px 0px}}.strong{font-family:\"Metropolis-Medium\",Helvetica,sans-serif}.bg-grey{background-color:#F2F2F2}.grid.three{display:grid;grid-template-columns:1fr 1fr 1fr;row-gap:20px;column-gap:20px}@media only screen and (max-width: 767px){.grid.three{grid-template-columns:1fr}}.grid.two{display:grid;grid-template-columns:1fr 1fr}@media only screen and (max-width: 767px){.grid.two{grid-template-columns:1fr}}.rounded-circle{border-radius:50% !important}noscript{background-color:rgba(255,0,0,0.25);display:block;font-size:14px;font-weight:bold;padding:10px;margin:0 20px 20px 0}.filter-blue{filter:brightness(0) saturate(100%) invert(39%) sepia(55%) saturate(4454%) hue-rotate(177deg) brightness(99%) contrast(104%)}@font-face{font-family:\"Metropolis-Bold\";src:url(\"/fonts/Metropolis-Bold.eot\");src:url(\"/fonts/Metropolis-Bold.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-Bold.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-Bold.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-BoldItalic\";src:url(\"/fonts/Metropolis-BoldItalic.eot\");src:url(\"/fonts/Metropolis-BoldItalic.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-BoldItalic.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-BoldItalic.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-Light\";src:url(\"/fonts/Metropolis-Light.eot\");src:url(\"/fonts/Metropolis-Light.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-Light.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-Light.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-LightItalic\";src:url(\"/fonts/Metropolis-LightItalic.eot\");src:url(\"/fonts/Metropolis-LightItalic.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-LightItalic.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-LightItalic.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-Regular\";src:url(\"/fonts/Metropolis-Regular.eot\");src:url(\"/fonts/Metropolis-Regular.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-Regular.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-Regular.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-RegularItalic\";src:url(\"/fonts/Metropolis-RegularItalic.eot\");src:url(\"/fonts/Metropolis-RegularItalic.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-RegularItalic.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-RegularItalic.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-Medium\";src:url(\"/fonts/Metropolis-Medium.eot\");src:url(\"/fonts/Metropolis-Medium.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-Medium.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-Medium.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-MediumItalic\";src:url(\"/fonts/Metropolis-MediumItalic.eot\");src:url(\"/fonts/Metropolis-MediumItalic.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-MediumItalic.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-MediumItalic.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-SemiBold\";src:url(\"/fonts/Metropolis-SemiBold.eot\");src:url(\"/fonts/Metropolis-SemiBold.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-SemiBold.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-SemiBold.woff\") format(\"woff\");font-weight:normal;font-style:normal}@font-face{font-family:\"Metropolis-SemiBoldItalic\";src:url(\"/fonts/Metropolis-SemiBoldItalic.eot\");src:url(\"/fonts/Metropolis-SemiBoldItalic.eot?#iefix\") format(\"embedded-opentype\"),url(\"/fonts/Metropolis-SemiBoldItalic.woff2\") format(\"woff2\"),url(\"/fonts/Metropolis-SemiBoldItalic.woff\") format(\"woff\");font-weight:normal;font-style:normal}.hero{background-color:#0091DA;color:#fff}.hero .text-block{max-width:550px;padding:0px 0px 10px 0px}.hero .text-block p{margin-bottom:20px;font-size:18px;color:#fff}.hero .text-block h2{font-size:36px}.hero.homepage{background-position:center center;background-repeat:no-repeat;background-size:cover;padding-bottom:80px}.hero.homepage h1{font-size:36px}@media only screen and (max-width: 767px){.hero .text-block{max-width:unset;margin-right:0px}.hero .button{display:block;text-align:center}.hero.homepage{background-image:none}}.grid-container{margin-top:-80px}.grid-container .grid.three{padding-bottom:20px}.grid-container .grid.three .card{position:relative;padding:30px 20px;background-color:#fff;text-align:center;box-shadow:0px 2px 10px rgba(0,0,0,0.2)}.grid-container .grid.three .card h3{color:#333;font-size:22px}.grid-container .grid.three .card p{color:#333}.introduction .grid.two{column-gap:140px;padding:35px 20px}.introduction .grid.two p{margin:0px;font-size:16px}.introduction .grid.two p.strong{color:#333}@media only screen and (max-width: 767px){.introduction{padding:0px 20px}.introduction .col:first-of-type{padding-bottom:50px}}.use-cases .grid{grid-template-columns:220px 1fr;margin-bottom:30px;grid-template-areas:\"image text\"}.use-cases .grid .image{background-color:#0091DA;text-align:center;display:flex;align-items:center;justify-content:center;grid-area:image}.use-cases .grid .image img{justify-self:center}.use-cases .grid .text{border:1px solid #F2F2F2;padding:30px;grid-area:text}.use-cases .grid .text a.button{display:block;max-width:138px;text-align:center;padding:5px 10px;min-width:unset}.use-cases .grid.image-right{grid-template-columns:1fr 220px;grid-template-areas:\"text image\"}@media only screen and (max-width: 767px){.use-cases .grid.image-right{grid-template-columns:1fr;grid-template-areas:\"image\" \"text\"}}@media only screen and (max-width: 767px){.use-cases .grid{grid-template-columns:1fr;grid-template-rows:minmax(160px, 1fr);grid-template-areas:\"image\" \"text\"}}.use-cases h2{color:#111}.use-cases p.strong{color:#1B3951;font-size:16px}.team{background-color:#1D428A}.team h2,.team h3,.team p{color:#fff}.team p{font-size:16px}.team a{color:#fff;font-weight:300;text-decoration:underline}.team .grid.three{row-gap:40px;margin:40px 0px}.team .bio{display:grid;grid-template-columns:120px 1fr;column-gap:20px}.team .bio .image img{height:120px;width:120px}.team .bio .info{align-self:center}.team .bio .info p{margin:0px}.team .bio .info p.name{font-size:16px;font-family:\"Metropolis-Medium\",Helvetica,sans-serif}.team .bio .info p.position{font-size:14px}.avatar{filter:grayscale(100%);transition:filter 0.6s ease-in-out}.avatar:hover{filter:grayscale(0%)}.hero.subpage-hero{background-position:center center;background-repeat:no-repeat;background-size:cover;padding-bottom:90px}.hero.subpage-hero h1{font-size:46px;text-align:center}@media only screen and (max-width: 767px){.hero.subpage-hero h1{font-size:26px}}.experimental .grid.three .col{padding:0px}.experimental .icon{background-color:#0091DA;padding:25px;min-height:95px;display:flex;align-items:center;justify-content:center}.experimental .content{padding:25px}.experimental .content .example{background-color:#F2F2F2}.blog{padding-bottom:50px}.blog .col{border:1px solid #F2F2F2}.blog .col img{width:100%}.blog .col .content{padding:0px 20px}.blog.landing{background-color:#fff;margin-top:-90px}.blog.landing h3 a{font-size:16px}.blog.landing .pagination{margin:30px auto 50px auto}.blog.landing .pagination ul{padding:0px;text-align:center}.blog.landing .pagination ul li{padding:0px}.blog.landing .pagination ul li a{padding:5px 10px}.blog.landing .pagination ul li a.active{background-color:#F2F2F2;border-radius:50%}.blog.landing .pagination ul li.left-arrow{margin-right:15px}.blog.landing .pagination ul li.right-arrow{margin-left:15px}.blog .blog-post{background-color:#fff;margin:-110px 0px 0px -30px;padding:30px 90px 30px 30px}.blog .blog-post .author{color:#0095D3;margin:0px}.blog .blog-post .date{color:#111;margin:0px;font-weight:600}.blog .blog-post .header,.blog .blog-post h4{color:#111;font-weight:600}.blog .blog-post a{font-size:16px}.blog .blog-post ul{list-style-type:disc;padding-left:20px}.blog .blog-post ul li{list-style-type:unset;display:list-item;margin-bottom:10px;font-size:14px;color:#333;line-height:1.6em;list-style-image:url(/img/arrow.svg)}.blog .blog-post ul li:first-child{margin-top:10px}.blog .blog-post ol li{list-style-type:decimal;display:list-item;margin-bottom:10px;font-size:16px;color:#333}.blog .blog-post ol li:first-child{margin-top:10px}.blog .blog-post code{border:2px solid #EFEFEF;color:#333;padding:2px 8px}.blog .blog-post pre code{display:block;border:15px solid #EFEFEF;padding:15px;margin-bottom:30px;overflow-x:auto}.blog .blog-post img{max-width:100%}.blog .blog-post strong{font-family:\"Metropolis-Medium\",Helvetica,sans-serif}.getting-started{background-color:#F2F2F2;color:#111}.getting-started p{color:#111;font-size:16px}.getting-started .left-side{width:50%;float:left}.getting-started .right-side{width:25%;float:right}.getting-started h2{font-size:30px;margin-bottom:0px}.getting-started a{display:block;max-width:138px;text-align:center;padding:10px;min-width:unset}.getting-started .button{margin-top:50px;border:1px solid #0095D3}@media only screen and (max-width: 767px){.getting-started .wrapper{padding-bottom:40px}.getting-started .left-side{width:100%;float:none}.getting-started .right-side{width:100%;float:none}.getting-started .button{display:block;text-align:center;max-width:unset;margin-top:20px}}.subpage{background-color:#fff;margin-top:-90px;padding:30px 30px 50px 30px}.subpage .section-header{margin-top:3rem;font-weight:600;font-size:20px}.subpage .embed-responsive{position:relative}.subpage .embed-responsive:before{padding-top:56.25%;display:block;content:\"\"}.subpage .embed-responsive .embed-responsive-item{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.subpage .grid{margin-bottom:20px}.subpage .grid .col{border:1px solid #F2F2F2}.subpage .grid .col .icon{display:flex;align-items:center;justify-content:center;min-height:140px}.subpage .grid .col .content{padding:0px 20px 20px 20px}.subpage .grid .col .content.plugins{padding-top:20px}.subpage .grid .col .content.plugins img{display:block;margin:0px auto 5px auto}.subpage .grid .col .content h3{margin-top:0px;text-align:center}.subpage .grid .col .content h3 a{font-size:20px}.subpage .grid .col .content ul{padding-left:20px}.subpage .grid .col .content ul li{margin-bottom:10px;color:#333;line-height:1.6em;list-style-image:url(/img/arrow.svg)}.docs{background-color:#fff;margin-top:-90px;padding:30px 30px 50px 30px;display:flex}.docs .side-nav{width:25%;float:left;position:relative}.docs .side-nav ul{padding-left:0px;margin-bottom:35px}.docs .side-nav ul li{display:list-item;margin-bottom:15px}.docs .side-nav ul li a{color:#333;font-size:14px}.docs .side-nav ul li a.active{color:#0095D3}.docs .side-nav ul li.heading{color:#111;font-size:14px}.docs .side-nav .dropdown{font-size:14px;font-family:\"Metropolis-Medium\",Helvetica,sans-serif;margin-bottom:10px}.docs .side-nav .dropdown button{background-image:url(/img/down-arrow.svg);background-repeat:no-repeat;background-position:90% center;border-radius:5px;display:inline;padding:10px 30px 10px 10px;border:1px solid #0095D3;color:#111;cursor:pointer;font-size:14px;font-family:\"Metropolis-Medium\",Helvetica,sans-serif;margin-bottom:10px}.docs .side-nav .dropdown button:focus{background-color:#F2F2F2}.docs .side-nav .dropdown-menu{position:absolute;border:1px solid #777;border-radius:5px;top:35px;left:0px;background-color:#fff;padding:10px 0;min-width:100px;display:none}.docs .side-nav .dropdown-menu a{display:block;padding:7px 20px}.docs .side-nav .dropdown-menu a:hover{background-color:#F2F2F2}.docs .side-nav .dropdown-menu.dropdown-menu-visible{display:block;z-index:1}.docs .side-nav .form-control{display:block;width:100%;height:40px;padding:.375rem .75rem;font-size:1.125rem;line-height:1.5;color:#333;background-color:#fff;border:1px solid #cecece;background-image:url(/img/search-icon.svg);background-repeat:no-repeat;background-position:95% center;border-radius:5px}.docs .side-nav .form-control:focus{outline:none}.docs .side-nav .form-control::-webkit-search-cancel-button{-webkit-appearance:none}.docs .side-nav .ds-dataset-1{padding:15px 15px 0}.docs .side-nav .ds-dataset-1 a{color:#333;display:inline-block;font-family:\"Metropolis-Light\",Helvetica,sans-serif;margin-bottom:10px}.docs .side-nav .ds-dataset-1 a div{display:inline}.docs .side-nav .ds-dataset-1 .algolia-docsearch-suggestion--subcategory-inline::after{content:' /'}.docs .side-nav .ds-dataset-1 .algolia-docsearch-suggestion--highlight{background-color:rgba(0,149,211,0.1);color:#1D428A}.docs .side-nav .ds-dataset-1 .algolia-docsearch-suggestion--title{font-family:\"Metropolis-Medium\",Helvetica,sans-serif}.docs .side-nav .ds-dataset-1 .algolia-docsearch-suggestion--category-header,.docs .side-nav .ds-dataset-1 .algolia-docsearch-suggestion--subcategory-column{display:none}.docs .side-nav .ds-dataset-1 .algolia-docsearch-footer{font-size:14px;text-align:right}.docs .side-nav .ds-dataset-1 .algolia-docsearch-footer a{font-size:14px}.docs .side-nav .ds-dropdown-menu{background-color:#fff;border:1px solid #cecece;border-radius:5px;width:130%}@media only screen and (min-width: 1440px){.docs .side-nav{width:22%}}@media only screen and (min-width: 1280px) and (max-width: 1439px){.docs .side-nav{width:22%}}.docs .docs-content{width:75%;float:right}.docs .docs-content.full{width:100%}.docs .docs-content a{font-size:16px}.docs .docs-content ul{list-style-type:disc;padding-left:20px}.docs .docs-content ul li{list-style-type:unset;display:list-item;margin-bottom:10px;font-size:16px;color:#333;line-height:1.6em;list-style-image:url(/img/arrow.svg)}.docs .docs-content ul li:first-child{margin-top:10px}.docs .docs-content ol li{list-style-type:decimal;display:list-item;margin-bottom:10px;font-size:16px;color:#333}.docs .docs-content ol li:first-child{margin-top:10px}.docs .docs-content code{border:2px solid #EFEFEF;color:#777;padding:2px 8px}.docs .docs-content pre{white-space:pre-wrap}.docs .docs-content pre code{display:block;border:15px solid #EFEFEF;padding:15px;margin-bottom:30px;overflow-x:auto}.docs .docs-content img{max-width:100%}@media only screen and (min-width: 1280px) and (max-width: 1439px){.docs .docs-content{width:58%;padding-right:20px}}@media only screen and (min-width: 1440px){.docs .docs-content{width:75%;padding-right:20px}}.docs .right-nav{width:20%;float:right;margin:-30px -30px 0px 0px}.docs .right-nav .right-nav-content{background-color:#EFEFEF;padding:30px 30px 30px 20px;margin-right:-20px;position:sticky;top:0}.docs .right-nav .buttons{margin-top:0px}.docs .right-nav .buttons li{margin-bottom:0px;padding:8px 0px;display:inline-block}.docs .right-nav .buttons li:first-of-type{border-right:1px solid #ddd;padding-right:5px}.docs .right-nav .buttons li a{text-transform:uppercase;font-size:14px}.docs .right-nav .buttons li a img{vertical-align:middle;width:22px}.docs .right-nav h4{font-size:16px}.docs .right-nav ul{padding-left:0px;margin-bottom:0px}.docs .right-nav ul li{display:block;padding-right:0px;margin-bottom:7px}.docs .right-nav ul li a{font-family:\"Metropolis-Light\",Helvetica,sans-serif;font-size:14px}.docs .right-nav ul li ul{margin-top:7px;padding-inline-start:14px}.docs .right-nav .sticky{position:fixed;top:0}@media only screen and (max-width: 1279px){.docs .right-nav{display:none}}\n\n/*# sourceMappingURL=style.css.map */"
  },
  {
    "path": "site/resources/_gen/assets/scss/scss/site.scss_8967e03afb92eb0cac064520bf021ba2.json",
    "content": "{\"Target\":\"css/style.css\",\"MediaType\":\"text/css\",\"Data\":{}}"
  },
  {
    "path": "site/themes/template/archetypes/default.md",
    "content": "+++\ntitle = \"{{ replace .Name \"-\" \" \" | title }}\"\ndate = {{ .Date }}\n+++\n"
  },
  {
    "path": "site/themes/template/assets/scss/_base.scss",
    "content": "@import 'variables';\n@import 'mixins';\n\n$font-family-base: \"Metropolis-Light\", Helvetica, sans-serif;\n$metropolis-light: $font-family-base;\n$metropolis-light-italic: \"Metropolis-LightItalic\", Helvetica, sans-serif;\n$metropolis-regular: \"Metropolis-Regular\", Helvetica, sans-serif;\n$metropolis-regular-italic: \"Metropolis-RegularItalic\", Helvetica, sans-serif;\n$metropolis-medium: \"Metropolis-Medium\", Helvetica, sans-serif;\n$metropolis-medium-italic: \"Metropolis-MediumItalic\", Helvetica, sans-serif;\n$metropolis-bold: \"Metropolis-Bold\", Helvetica, sans-serif;\n$metropolis-bold-italic: \"Metropolis-BoldItalic\", Helvetica, sans-serif;\n$metropolis-semibold: \"Metropolis-SemiBold\", Helvetica, sans-serif;\n$metropolis-semibold-italic: \"Metropolis-SemiBoldItalic\", Helvetica, sans-serif;\n\nbody {\n    font-family: $font-family-base;\n    margin: 0px;\n    line-height: 1.25;\n}\n.wrapper {\n    max-width: 980px;\n    margin: 0px auto;\n    padding: 20px;\n    @include breakpoint(small) {\n        max-width: 100%;\n    }\n    @include breakpoint(medium) {\n    }\n    &.docs {\n        @include breakpoint(extra-large) {\n            max-width: 80%;\n        }\n    }\n}\n.clearfix {\n\t*zoom: 1;\n\t&:before, &:after {\n\t\tdisplay: table;\n\t\tcontent: \"\";\n\t\tline-height: 0;\n\t}\n\t&:after {\n\t\tclear: both;\n\t}\n}\nh1, h2, h3, h4, h5, h6 {\n    font-weight: 300;\n}\nh1 {\n    font-size: 28px;\n}\nh2 {\n    font-size: 22px;\n    color: #333;\n}\nh3 {\n    font-size: 20px;\n}\nh4 {\n    font-size: 18px;\n}\nli {\n    list-style-type: none;\n    display: inline;\n    padding-right: 25px;\n    font-size: 14px;\n    line-height: 1.7em;\n    &:last-of-type {\n        padding-right: 0px;\n    }\n}\np {\n    line-height: 1.7em;\n    font-weight: 300;\n    font-size: 16px;\n    color: $darkgrey;\n    &.intro {\n        font-size: 18px;\n    }\n}\na {\n    font-size: 16px;\n    text-decoration: none;\n    color: $blue;\n    font-family: $metropolis-medium\n}\nbutton {\n    background-color: unset;\n    border: none;\n}\n.button {\n    color: $blue;\n    font-size: 12px;\n    font-weight: 600;\n    background-color: $white;\n    border-radius: 3px;\n    padding: 14px 10px;\n    min-width: 200px;\n    text-transform: uppercase;\n    border: 1px solid $white;\n    &.secondary {\n        background-color: $mainblue;\n        color: $white;\n    }\n    &.tertiary {\n        border: 1px solid $blue;\n    }\n}\n.buttons {\n    margin-top: 40px;\n    .button:first-of-type {\n        margin-right: 30px;\n        @include breakpoint(small) {\n            margin: 0px 0px 20px 0px;\n        }\n    }\n}\n.strong {\n    font-family: $metropolis-medium;\n}\n.bg-grey {\n    background-color: $lightgrey;\n}\n\n.grid.three {\n    display: grid;\n    grid-template-columns: 1fr 1fr 1fr;\n    row-gap: 20px;\n    column-gap: 20px;\n    @include breakpoint(small) {\n        grid-template-columns: 1fr;\n    }\n}\n\n.grid.two {\n    display: grid;\n    grid-template-columns: 1fr 1fr;\n    @include breakpoint(small) {\n        grid-template-columns: 1fr;\n    }\n}\n\n.rounded-circle {\n    border-radius: 50% !important;\n}\n\nnoscript {\n    background-color: rgba(red, .25);\n    display: block;\n    font-size: 14px;\n    font-weight: bold;\n    padding: 10px;\n    margin: 0 20px 20px 0;\n}\n\n.filter-blue{\n    filter: brightness(0) saturate(100%) invert(39%) sepia(55%) saturate(4454%) hue-rotate(177deg) brightness(99%) contrast(104%);\n}\n\n// Metropolis\n@font-face {\n    font-family: \"Metropolis-Bold\";\n    src:url(\"/fonts/Metropolis-Bold.eot\");\n    src:url(\"/fonts/Metropolis-Bold.eot?#iefix\") format(\"embedded-opentype\"),\n        url(\"/fonts/Metropolis-Bold.woff2\") format(\"woff2\"),\n        url(\"/fonts/Metropolis-Bold.woff\") format(\"woff\");\n    font-weight: normal;\n    font-style: normal;\n  }\n  \n  @font-face {\n    font-family: \"Metropolis-BoldItalic\";\n    src:url(\"/fonts/Metropolis-BoldItalic.eot\");\n    src:url(\"/fonts/Metropolis-BoldItalic.eot?#iefix\") format(\"embedded-opentype\"),\n        url(\"/fonts/Metropolis-BoldItalic.woff2\") format(\"woff2\"),\n        url(\"/fonts/Metropolis-BoldItalic.woff\") format(\"woff\");\n    font-weight: normal;\n    font-style: normal;\n  }\n  \n  @font-face {\n    font-family: \"Metropolis-Light\";\n    src:url(\"/fonts/Metropolis-Light.eot\");\n    src:url(\"/fonts/Metropolis-Light.eot?#iefix\") format(\"embedded-opentype\"),\n        url(\"/fonts/Metropolis-Light.woff2\") format(\"woff2\"),\n        url(\"/fonts/Metropolis-Light.woff\") format(\"woff\");\n    font-weight: normal;\n    font-style: normal;\n  }\n  \n  @font-face {\n    font-family: \"Metropolis-LightItalic\";\n    src:url(\"/fonts/Metropolis-LightItalic.eot\");\n    src:url(\"/fonts/Metropolis-LightItalic.eot?#iefix\") format(\"embedded-opentype\"),\n        url(\"/fonts/Metropolis-LightItalic.woff2\") format(\"woff2\"),\n        url(\"/fonts/Metropolis-LightItalic.woff\") format(\"woff\");\n    font-weight: normal;\n    font-style: normal;\n  }\n  \n  @font-face {\n    font-family: \"Metropolis-Regular\";\n    src:url(\"/fonts/Metropolis-Regular.eot\");\n    src:url(\"/fonts/Metropolis-Regular.eot?#iefix\") format(\"embedded-opentype\"),\n        url(\"/fonts/Metropolis-Regular.woff2\") format(\"woff2\"),\n        url(\"/fonts/Metropolis-Regular.woff\") format(\"woff\");\n    font-weight: normal;\n    font-style: normal;\n  }\n  \n  @font-face {\n    font-family: \"Metropolis-RegularItalic\";\n    src:url(\"/fonts/Metropolis-RegularItalic.eot\");\n    src:url(\"/fonts/Metropolis-RegularItalic.eot?#iefix\") format(\"embedded-opentype\"),\n        url(\"/fonts/Metropolis-RegularItalic.woff2\") format(\"woff2\"),\n        url(\"/fonts/Metropolis-RegularItalic.woff\") format(\"woff\");\n    font-weight: normal;\n    font-style: normal;\n  }\n  \n  @font-face {\n    font-family: \"Metropolis-Medium\";\n    src:url(\"/fonts/Metropolis-Medium.eot\");\n    src:url(\"/fonts/Metropolis-Medium.eot?#iefix\") format(\"embedded-opentype\"),\n        url(\"/fonts/Metropolis-Medium.woff2\") format(\"woff2\"),\n        url(\"/fonts/Metropolis-Medium.woff\") format(\"woff\");\n    font-weight: normal;\n    font-style: normal;\n  }\n  \n  @font-face {\n    font-family: \"Metropolis-MediumItalic\";\n    src:url(\"/fonts/Metropolis-MediumItalic.eot\");\n    src:url(\"/fonts/Metropolis-MediumItalic.eot?#iefix\") format(\"embedded-opentype\"),\n        url(\"/fonts/Metropolis-MediumItalic.woff2\") format(\"woff2\"),\n        url(\"/fonts/Metropolis-MediumItalic.woff\") format(\"woff\");\n    font-weight: normal;\n    font-style: normal;\n  }\n  \n  @font-face {\n    font-family: \"Metropolis-SemiBold\";\n    src:url(\"/fonts/Metropolis-SemiBold.eot\");\n    src:url(\"/fonts/Metropolis-SemiBold.eot?#iefix\") format(\"embedded-opentype\"),\n        url(\"/fonts/Metropolis-SemiBold.woff2\") format(\"woff2\"),\n        url(\"/fonts/Metropolis-SemiBold.woff\") format(\"woff\");\n    font-weight: normal;\n    font-style: normal;\n  }\n  \n  @font-face {\n    font-family: \"Metropolis-SemiBoldItalic\";\n    src:url(\"/fonts/Metropolis-SemiBoldItalic.eot\");\n    src:url(\"/fonts/Metropolis-SemiBoldItalic.eot?#iefix\") format(\"embedded-opentype\"),\n        url(\"/fonts/Metropolis-SemiBoldItalic.woff2\") format(\"woff2\"),\n        url(\"/fonts/Metropolis-SemiBoldItalic.woff\") format(\"woff\");\n    font-weight: normal;\n    font-style: normal;\n  }"
  },
  {
    "path": "site/themes/template/assets/scss/_components.scss",
    "content": "@import 'variables';\n@import 'mixins';\n\n/* Homepage Hero */\n.hero {\n    background-color: $mainblue;\n    color: $white;\n    .text-block {\n        max-width: 7000px;\n        padding: 0px 0px 10px 0px;\n        p {\n            margin-bottom: 20px;\n            font-size: 18px;\n            color: $white;\n        }\n        h2 {\n            font-size: 36px;\n        }\n    }\n    &.homepage {\n        //background-image: url(/img/hero-image.png);\n        background-position: center center;\n        background-repeat: no-repeat;\n        background-size: cover;\n        padding-bottom: 80px;\n        h1 {\n            font-size: 36px;\n        }\n    }\n    @include breakpoint(small) {\n        .text-block {\n            max-width: unset;\n            margin-right: 0px;\n        }\n        .button {\n            display: block;\n            text-align: center;\n        }\n        &.homepage {\n            background-image: none;\n        }\n    }\n}\n\n.grid-container {\n    margin-top: -80px;\n    .grid.three {\n        padding-bottom: 20px;\n        .card {\n            position: relative;\n            padding: 30px 20px;\n            background-color: $white;\n            text-align: center;\n            box-shadow: 0px 2px 10px rgba(0,0,0,0.2);\n            h3 {\n                color: $darkgrey;\n                font-size: 22px;\n            }\n            p {\n                color: $darkgrey;\n            }\n        }\n    }\n}\n\n.introduction {\n    .grid.two {\n        column-gap: 140px;\n        padding: 35px 20px;\n        p {\n            margin: 0px;\n            font-size: 16px;\n            &.strong {\n                color: $darkgrey;\n            }\n        }\n    }\n    @include breakpoint(small) {\n        padding: 0px 20px;\n        .col:first-of-type {\n            padding-bottom: 50px;\n        }\n    }\n}\n\n.use-cases {\n    .grid { \n        grid-template-columns: 220px 1fr;\n        margin-bottom: 30px;\n        grid-template-areas:\n                \"image text\";\n        .image {\n            background-color: $mainblue;\n            text-align: center;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            grid-area: image;\n            img {\n                justify-self: center;\n            }\n        }\n        .text {\n            border: 1px solid $lightgrey;\n            padding: 30px;\n            grid-area: text;\n            a.button {\n                display: block;\n                max-width: 138px;\n                text-align: center;\n                padding: 5px 10px;\n                min-width: unset;\n            }\n        }\n        &.image-right {\n            grid-template-columns: 1fr 220px;\n            grid-template-areas:\n                \"text image\";\n            @include breakpoint(small) {\n                grid-template-columns: 1fr;\n                grid-template-areas:\n                    \"image\"\n                    \"text\";\n            }\n        }\n        @include breakpoint(small) {\n            grid-template-columns: 1fr;\n            grid-template-rows: minmax(160px, 1fr);\n            grid-template-areas:\n                \"image\"\n                \"text\";\n        }\n    }\n    h2 {\n        color: $black;\n    }\n    p.strong {\n        color: #1B3951;\n        font-size: 16px;\n    }\n}\n\n.team {\n    background-color: $navyblue;\n    h2, h3, p {\n        color: $white;\n    }\n    p {\n        font-size: 16px;\n    }\n    a {\n        color: $white;\n        font-weight: 300;\n        text-decoration: underline;\n    }\n    .grid.three {\n        row-gap: 40px;\n        margin: 40px 0px;\n    }\n    .bio {\n        display: grid;  \n        grid-template-columns: 120px 1fr;\n        column-gap: 20px;\n        .image img {\n            height: 120px;\n            width: 120px;\n        }\n        .info {\n            align-self: center;\n            p {\n                margin: 0px;\n                &.name {\n                    font-size: 16px;\n                    font-family: $metropolis-medium;\n                }\n                &.position {\n                    font-size: 14px;\n                }\n            }\n        }\n    }\n}\n\n.avatar {\n    filter: grayscale(100%);\n    transition: filter 0.6s ease-in-out;\n\n    &:hover {\n      filter: grayscale(0%);\n    }\n  }\n\n.hero.subpage-hero {\n    //background-image: url(/img/blog-hero-image.png);\n    background-position: center center;\n    background-repeat: no-repeat;\n    background-size: cover;\n    padding-bottom: 90px;\n\n    h1 {\n        font-size: 46px;\n        text-align: center;\n        @include breakpoint(small) {\n            font-size: 26px;\n        }\n    }\n}\n\n.experimental {\n    .grid.three .col {\n        padding: 0px;\n    }\n    .icon {\n        background-color: $mainblue;\n        padding: 25px;\n        min-height: 95px;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n    }\n    .content {\n        padding: 25px;\n        .example {\n            background-color: $lightgrey;\n        }\n    }\n}\n\n.blog {\n    padding-bottom: 50px;\n    .col {\n        border: 1px solid $lightgrey;\n        img {\n            width: 100%;\n        }\n        .content {\n            padding: 0px 20px;\n        }\n    }\n    &.landing {\n        background-color: #fff;\n        margin-top: -90px;\n        h3 a {\n            font-size: 16px;\n        }\n        .pagination {\n            margin: 30px auto 50px auto;\n            ul {\n                padding: 0px;\n                text-align: center;\n                li {\n                    padding: 0px;\n                    a {\n                        padding: 5px 10px;\n                        &.active {\n                            background-color: $lightgrey;\n                            border-radius: 50%;\n                        }\n                    }\n                    &.left-arrow {\n                        margin-right: 15px;\n                    }\n                    &.right-arrow {\n                        margin-left: 15px;\n                    }\n                }\n            }\n        }\n    }\n    .blog-post {\n        background-color: #fff;\n        margin: -110px 0px 0px -30px;\n        padding: 30px 90px 30px 30px;\n        .author {\n            color: $blue;\n            margin: 0px;\n        }\n        .date {\n            color: $black;\n            margin: 0px;\n            font-weight: 600;\n        }\n        .header, h4 {\n            color: $black;\n            font-weight: 600;\n        }\n        a {\n            font-size: 16px;\n        }\n        ul {\n            list-style-type: disc;\n            padding-left: 20px;\n            li {\n                list-style-type: unset;\n                display: list-item;\n                margin-bottom: 10px;\n                font-size: 14px;\n                color: $darkgrey;\n                line-height: 1.6em;\n                list-style-image: url(/img/arrow.svg);\n                &:first-child {\n                    margin-top: 10px;\n                }\n            }\n        }\n        ol {\n            li {\n                list-style-type: decimal;\n                display: list-item;\n                margin-bottom: 10px;\n                font-size: 16px;\n                color: $darkgrey;\n                &:first-child {\n                    margin-top: 10px;\n                }\n            }\n        }\n        code {\n            border: 2px solid #EFEFEF;\n            color: $darkgrey;\n            padding: 2px 8px;\n        }\n        pre {\n            code {\n                display: block;\n                border: 15px solid #EFEFEF;\n                padding: 15px;\n                margin-bottom: 30px;\n                overflow-x: auto;\n            }\n        }\n        img {\n            max-width: 100%;\n        }\n        strong {\n            font-family: $metropolis-medium;\n        }\n    }\n}\n\n.getting-started {\n    background-color: $lightgrey;\n    color: $black;\n    p {\n        color: $black;\n        font-size: 16px;\n    }\n    .left-side {\n        width: 50%;\n        float: left;\n    }\n    .right-side {\n        width: 25%;\n        float: right;\n    }\n    h2 {\n        font-size: 30px;\n        margin-bottom: 0px;\n    }\n    a {\n        display: block;\n        max-width: 138px;\n        text-align: center;\n        padding: 10px;\n        min-width: unset;\n    }\n    .button {\n        margin-top: 50px;\n        border: 1px solid $blue;\n    }\n    @include breakpoint(small) {\n        .wrapper {\n            padding-bottom: 40px;\n        }\n        .left-side {\n            width: 100%;\n            float: none;\n        }\n        .right-side {\n            width: 100%;\n            float: none;\n        }\n        .button {\n            display: block;\n            text-align: center;\n            max-width: unset;\n            margin-top: 20px;\n        }\n    }\n}\n\n.subpage {\n    background-color: #fff;\n    margin-top: -90px;\n    padding: 30px 30px 50px 30px;\n    .section-header {\n        margin-top: 3rem;\n        font-weight: 600;\n        font-size: 20px;\n    }\n    .embed-responsive {\n        position: relative;\n        &:before {\n            padding-top: 56.25%;\n            display: block;\n            content: \"\";\n        }\n        .embed-responsive-item {\n            position: absolute;\n            top: 0;\n            bottom: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            border: 0;\n        }\n    }\n    .grid {\n        margin-bottom: 20px;\n        .col {\n            border: 1px solid #F2F2F2;\n            .icon {\n                display: flex;\n                align-items: center;\n                justify-content: center;\n                min-height: 140px;\n            }\n            .content {\n                padding: 0px 20px 20px 20px;\n                &.plugins {\n                    padding-top: 20px;\n                    img {\n                        display: block;\n                        margin: 0px auto 5px auto;\n                    }\n                }\n                h3 {\n                    margin-top: 0px;\n                    text-align: center;\n\n                    a {\n                        font-size: 20px;\n                    }\n                }\n                ul {\n                    padding-left: 20px;\n                    li {\n                        margin-bottom: 10px;\n                        color: $darkgrey;\n                        line-height: 1.6em;\n                        list-style-image: url(/img/arrow.svg);\n                    }\n                }\n            }\n        }\n    }\n}\n\n.docs {\n    background-color: #fff;\n    margin-top: -90px;\n    padding: 30px 30px 50px 30px;\n    display: flex;\n    .side-nav {\n        width: 25%;\n        float: left;\n        position: relative;\n        ul {\n            padding-left: 0px;\n            margin-bottom: 35px;\n            li {\n                display: list-item;\n                margin-bottom: 15px;\n                a {\n                    color: $darkgrey;\n                    font-size: 14px;\n                    &.active {\n                        color: $blue;\n                    }\n                }\n                &.heading {\n                    color: $black;\n                    font-size: 14px;\n                }\n            }\n        }\n        .dropdown {\n            font-size: 14px;\n            font-family: $metropolis-medium;\n            margin-bottom: 10px;\n            button {\n                background-image: url(/img/down-arrow.svg);\n                background-repeat: no-repeat;\n                background-position: 90% center;\n                border-radius: 5px;\n                display: inline;\n                padding: 10px 30px 10px 10px;\n                border: 1px solid $blue;\n                color: $black;\n                cursor: pointer;\n                font-size: 14px;\n                font-family: $metropolis-medium;\n                margin-bottom: 10px;\n                &:focus {\n                    background-color: $lightgrey;\n                }\n            }\n        }\n        .dropdown-menu {\n            position: absolute;\n            border: 1px solid $grey;\n            border-radius: 5px;\n            top: 35px;\n            left: 0px;\n            background-color: $white;\n            padding: 10px 0;\n            min-width: 100px;\n            display: none;\n            a {\n                display: block;\n                padding: 7px 20px;\n                &:hover {\n                    background-color: $lightgrey;\n                }\n            }\n            &.dropdown-menu-visible {\n                display: block;\n                z-index: 1;\n            }\n        }\n        .form-control {\n            display: block;\n            width: 100%;\n            height: 40px;\n            padding: .375rem .75rem;\n            font-size: 1.125rem;\n            line-height: 1.5;\n            color: $darkgrey;\n            background-color: #fff;\n            border: 1px solid #cecece;\n            background-image: url(/img/search-icon.svg);\n            background-repeat: no-repeat;\n            background-position: 95% center;\n            border-radius: 5px;\n            &:focus {\n                outline: none;\n            }\n            &::-webkit-search-cancel-button {\n                -webkit-appearance: none;\n            }\n        }\n        .ds-dataset-1 {\n            padding: 15px 15px 0;\n\n            a {\n                color: $darkgrey;\n                display: inline-block;\n                font-family: $metropolis-light;\n                margin-bottom: 10px;\n\n                div {\n                    display: inline;\n                }\n            }\n\n            .algolia-docsearch-suggestion--subcategory-inline {\n                &::after {\n                    content: ' /';\n                }\n            }\n\n            .algolia-docsearch-suggestion--highlight {\n                background-color: rgba($blue, .1);\n                color: $navyblue;\n            }\n\n            .algolia-docsearch-suggestion--title {\n                font-family: $metropolis-medium;\n            }\n\n            .algolia-docsearch-suggestion--category-header,\n            .algolia-docsearch-suggestion--subcategory-column {\n                display: none;\n            }\n\n            .algolia-docsearch-footer {\n                font-size: 14px;\n                text-align: right;\n\n                a {\n                    font-size: 14px;\n                }\n            }\n        }\n        .ds-dropdown-menu {\n            background-color: #fff;\n            border: 1px solid #cecece;\n            border-radius: 5px;\n            width: 130%;\n        }\n        @include breakpoint(extra-large) {\n            width: 22%;\n        }\n        @include breakpoint(large) {\n            width: 22%;\n        }\n    }\n    .docs-content {\n        width: 75%;\n        float: right;\n        &.full {\n            width: 100%;\n        }\n        a {\n            font-size: 16px;\n        }\n        ul {\n            list-style-type: disc;\n            padding-left: 20px;\n            li {\n                list-style-type: unset;\n                display: list-item;\n                margin-bottom: 10px;\n                font-size: 16px;\n                color: $darkgrey;\n                line-height: 1.6em;\n                list-style-image: url(/img/arrow.svg);\n                &:first-child {\n                    margin-top: 10px;\n                }\n            }\n        }\n        ol {\n            li {\n                list-style-type: decimal;\n                display: list-item;\n                margin-bottom: 10px;\n                font-size: 16px;\n                color: $darkgrey;\n                &:first-child {\n                    margin-top: 10px;\n                }\n            }\n        }\n        code {\n            border: 2px solid #EFEFEF;\n            color: $grey;\n            padding: 2px 8px;\n        }\n        pre {\n            white-space: pre-wrap;\n            code {\n                display: block;\n                border: 15px solid #EFEFEF;\n                padding: 15px;\n                margin-bottom: 30px;\n                overflow-x: auto;\n            }\n        }\n        img {\n            max-width: 100%;\n        }\n        @include breakpoint(large) {\n            width: 58%;\n            padding-right: 20px;\n        }\n        @include breakpoint(extra-large) {\n            width: 75%;\n            padding-right: 20px;\n        }\n    }\n    .right-nav {\n        width: 20%;\n        float: right;\n        margin: -30px -30px 0px 0px;\n        .right-nav-content {\n            background-color: #EFEFEF;\n            padding: 30px 30px 30px 20px;\n            margin-right: -20px;\n            position: sticky;\n            top: 0;\n        }\n        .buttons {\n            margin-top: 0px;\n            li {\n                margin-bottom: 0px;\n                padding: 8px 0px;\n                display: inline-block;\n                &:first-of-type {\n                    border-right: 1px solid #ddd;\n                    padding-right: 5px;\n                }\n                a {\n                    text-transform: uppercase;\n                    font-size: 14px;\n                    img {\n                        vertical-align: middle;\n                        width: 22px;\n                    }\n                }\n            }\n        }\n        h4 {\n            font-size: 16px;\n        }\n        ul {\n            padding-left: 0px;\n            margin-bottom: 0px;\n            li {\n                display: block;\n                padding-right: 0px;\n                margin-bottom: 7px;\n                a {\n                    font-family: $metropolis-light;\n                    font-size: 14px;\n                }\n                ul {\n                    margin-top: 7px;\n                    padding-inline-start: 14px;\n                }\n            }\n        }\n        .sticky {\n            position: fixed;\n            top: 0;\n        }\n        @include breakpoint(small-medium) {\n            display: none;\n        }\n    }\n}\n"
  },
  {
    "path": "site/themes/template/assets/scss/_footer.scss",
    "content": "@import 'variables';\n@import 'mixins';\n@import 'base';\n\nfooter {\n    .top-links {\n        min-height: 52px;\n        display: flex;\n        align-items: center;\n        justify-content: space-between;\n    }\n    .left-links {\n        padding: 0px;\n        li {\n            img {\n                vertical-align: bottom;\n                margin-right: 10px;\n            }\n            a {\n                color: $darkgrey;\n                font-weight: 300;\n                font-size: 12px;\n                font-family: $metropolis-light;\n            }\n        }\n        .mobile {\n            display: none;\n        }\n    }\n    .right-links {\n        p {\n            margin: 0px;\n        }\n        .copywrite {\n            font-size: 12px;\n            padding-right: 10px;\n            a {\n                font-size: 12px;\n                color: $darkgrey;\n                font-family: $metropolis-light;\n            }\n        }\n        a {\n            vertical-align: middle;\n        }\n    }\n    .bottom-links {\n        margin: 10px 0px 30px 0px;\n        p {\n            display: flex;\n            flex-wrap: wrap;\n            justify-content: space-between;\n            font-size: 12px;\n\n            .ot-sdk-show-settings {\n                cursor: pointer;\n            }\n        }\n        a {\n            font-size: 12px;\n            font-family: $metropolis-light;\n            text-decoration: underline;\n        }\n        img {\n            max-width: 75px;\n            vertical-align: middle;\n            margin-left: 30px;\n        }\n        .footer-logo {\n            height: 3em;\n        }\n    }\n    @include breakpoint(small) {\n        .footer-links {\n            display: block;\n            .right-links {\n                display: none;\n            }\n            .left-links {\n                float: none;\n                margin: 10px 0px;\n                .desktop {\n                    display: none;\n                }\n                .mobile {\n                    display: inline;\n                }\n                .copywrite {\n                    display: block;\n                    margin-top:20px;\n                }\n            }\n        }\n        .bottom-links {\n            margin: 10px 0px 20px 0px;\n            float: none;\n            img {\n                margin-left: 0px;\n                display: block;\n                margin-top: 10px;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "site/themes/template/assets/scss/_header.scss",
    "content": "@import 'variables';\n@import 'mixins';\n@import 'base';\n\nheader {\n    .wrapper {\n        padding: 10px 20px;\n        min-height: 52px;\n        display: flex;\n        align-items: center;\n        justify-content: space-between;\n    }\n    .desktop-links {\n        padding-left: 0px;\n    }\n    a {\n        color: $darkgrey;\n        font-family: $metropolis-light;\n        &.active {\n            font-family: $metropolis-medium;\n        }\n    }\n    li img {\n        vertical-align: bottom;\n        margin-right: 10px;\n    }\n    .mobile {\n       display: none;\n    }\n    @include breakpoint(medium) {\n        .desktop-links li {\n            padding-right: 10px;\n        }\n    }\n    @include breakpoint(small) {\n        .expanded-icon {\n            display: none;\n            padding: 11px 3px 0px 0px;\n        }\n        .collapsed-icon {\n            padding-top: 12px;\n        }\n        .mobile-menu-visible {\n            .mobile {\n                display: block;\n                .collapsed-icon {\n                    display: none;\n                }\n                .expanded-icon {\n                    display: block;\n                }\n            }\n        }\n        position: relative;\n        .desktop-links {\n            display: none;\n        }\n        .mobile {\n            display: block;\n        }\n        button {\n            float: right;\n            &:focus {\n                outline: none;\n            }\n        }\n        ul {\n            padding-left: 0px;\n            li {\n                display: block;\n                margin: 20px 0px;\n            }\n        }\n        .mobile-menu {\n            position: absolute;\n            background-color: #fff;\n            width: 100%;\n            top: 70px;\n            left: 0px;\n            padding-bottom: 20px;\n            display: none;\n            z-index: 10;\n            .header-links {\n                margin: 0px 20px;\n            }\n            .social {\n                margin: 0px 20px;\n                padding-top: 20px;\n                img {\n                    vertical-align: middle;\n                    padding-right: 10px;\n                }\n                a {\n                    font-size: 14px;\n                    padding-right: 35px;\n                    &:last-of-type {\n                        padding-right: 0px;\n                    }\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "site/themes/template/assets/scss/_mixins.scss",
    "content": "@mixin breakpoint($point) {\n\t$small: 767px; // Up to 767px\n    $medium: 1279px; // Up to 1279px\n    $large: 1439px; // Up to 1439px\n    $extra-large: 1800px; // Up to 1800px\n\t@if $point == extra-large {\n\t\t@media only screen and (min-width : $large+1) { @content; }\n    }\n    @else if $point == large {\n\t\t@media only screen and (min-width : $medium+1) and (max-width: $large) { @content; }\n\t}\n\t@else if $point == medium-large {\n\t\t@media only screen and (min-width: $medium+1) { @content; }\n\t}\n\t@else if $point == medium {\n\t\t@media only screen and (min-width: $small+1) and (max-width: $medium) { @content; }\n\t}\n\t@else if $point == small-medium {\n\t\t@media only screen and (max-width: $medium) { @content; }\n\t}\n\t@else if $point == small {\n\t\t@media only screen and (max-width: $small) { @content; }\n\t}\n}\n\n@mixin clearfix {\n\t*zoom: 1;\n\t&:before, &:after {\n\t\tdisplay: table;\n\t\tcontent: \"\";\n\t\tline-height: 0;\n\t}\n\t&:after {\n\t\tclear: both;\n\t}\n}"
  },
  {
    "path": "site/themes/template/assets/scss/_variables.scss",
    "content": "$white: #ffffff;\n$blue: #0095D3;\n$darkgrey: #333333;\n$grey: #777777;\n$lightgrey: #F2F2F2;\n$darkblue: #002538;\n$purple: #7F35B2;\n$black: #111111;\n$mainblue: #0091DA;\n$navyblue: #1D428A;"
  },
  {
    "path": "site/themes/template/assets/scss/site.scss",
    "content": "@import 'header';\n@import 'footer';\n@import 'base';\n@import 'variables';\n@import 'components';\n@import 'mixins';"
  },
  {
    "path": "site/themes/template/layouts/_default/_markup/render-image.html",
    "content": "{{ $link := .Destination }}\n{{ if not (strings.HasPrefix $link \"http\") }}\n    {{ if strings.HasSuffix .Page.Parent.RelPermalink \"docs/\" }}\n        {{ $link = printf \"%s%s\" .Page.RelPermalink .Destination }}\n    {{ else }}\n        {{ $link = printf \"%s%s\" .Page.Parent.RelPermalink .Destination }}\n    {{ end }}\n{{ end }}\n<p>\n    <img src=\"{{ .Destination | safeURL }}\" alt=\"{{ .Text }}\" {{ with .Title}} title=\"{{ . }}\"{{ end }} />\n</p>"
  },
  {
    "path": "site/themes/template/layouts/_default/_markup/render-link.html",
    "content": "{{ $link := .Destination }}\n{{ $isRemote := strings.HasPrefix $link \"http\" }}\n{{- if not $isRemote -}}\n{{ $url := urls.Parse .Destination }}\n{{- if $url.Path -}}\n{{ $fragment := \"\" }}\n{{- with $url.Fragment }}{{ $fragment = printf \"#%s\" . }}{{ end -}}\n{{- with .Page.GetPage $url.Path }}{{ $link = printf \"%s%s\" .RelPermalink $fragment }}{{ end }}{{ end -}}\n{{- end -}}\n<a href=\"{{ $link | safeURL }}\"{{ with .Title}} title=\"{{ . }}\"{{ end }}{{ if $isRemote }} target=\"_blank\"{{ end }}>{{ .Text | safeHTML }}</a>"
  },
  {
    "path": "site/themes/template/layouts/_default/baseof.html",
    "content": "<!DOCTYPE html>\n<html lang=\"{{ .Site.LanguageCode | default \"en-us\" }}\">\n<head>\n\t<meta http-equiv=\"refresh\" content=\"0; url=https://github.com/bitnami-labs/sealed-secrets/\" />\n</head>\n<body>\n</body>\n</html>\n"
  },
  {
    "path": "site/themes/template/layouts/_default/docs.html",
    "content": "{{ define \"main\" }}\n\t<main>\n\t\t<div class=\"hero subpage-hero\">\n\t\t\t<div class=\"wrapper\">\n\t\t\t\t<h1>Documentation</h1>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"wrapper docs clearfix\">\n\t\t\t{{ partial \"docs-sidebar.html\" . }}\n\t\t\t<div class=\"docs-content {{ if not .Site.Params.docs_right_sidebar }}full{{ end }}\">\n\t\t\t\t{{ .Content }}\n\t\t\t</div>\n\t\t\t{{ partial \"docs-right-bar.html\" . }}\n\t\t</div>\n\t</main>\n{{ end }}"
  },
  {
    "path": "site/themes/template/layouts/_default/list.html",
    "content": "{{ define \"main\" }}\n\t<main>\n\t\t{{ if or .Title .Content }}\n\t\t<div>\n\t\t\t{{ with .Title }}<h1>{{ . }}</h1>{{ end }}\n\t\t\t{{ with .Content }}<div>{{ . }}</div>{{ end }}\n\t\t</div>\n\t\t{{ end }}\n\n\t\t{{ range .Paginator.Pages }}\n\t\t\t{{ .Render \"summary\" }}\n\t\t{{ end }}\n\t</main>\n{{ end }}\n"
  },
  {
    "path": "site/themes/template/layouts/_default/posts.html",
    "content": "{{ define \"main\" }}\n\t<main>\n\t\t<div class=\"hero subpage-hero\">\n\t\t\t<div class=\"wrapper\">\n\t\t\t\t<h1>Blog</h1>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"wrapper blog landing\">\n\t\t\t<div class=\"grid three\">\n\t\t\t\t{{ range (.Paginator 9).Pages.ByDate }}\n\t\t\t\t\t{{ partial \"blog-post-card.html\" . }}\n\t\t\t\t{{ end }}\n\t\t\t</div>\n\t\t\t{{ partial \"pagination.html\" . }}\n\t\t</div>\n\t</main>\n{{ end }}\n"
  },
  {
    "path": "site/themes/template/layouts/_default/search.html",
    "content": "{{ if .Site.Params.docs_search }}\n    <form class=\"d-flex align-items-center\">\n        <span class=\"algolia-autocomplete\" style=\"position: relative; display: inline-block; direction: ltr;\">\n        <input type=\"search\" class=\"form-control docsearch-input\" id=\"search-input\" placeholder=\"Search...\"\n                aria-label=\"Search for...\" autocomplete=\"off\" spellcheck=\"false\" role=\"combobox\"\n                aria-autocomplete=\"list\" aria-expanded=\"false\" aria-owns=\"algolia-autocomplete-listbox-0\"\n                dir=\"auto\" style=\"position: relative; vertical-align: top;\">\n        </span>\n    </form>\n{{ end }}"
  },
  {
    "path": "site/themes/template/layouts/_default/section.html",
    "content": "{{ define \"main\" }}\n\t<main>\n\t\t{{ .Content }}\t\t\n\t</main>\n{{ end }}\n\n\n\n"
  },
  {
    "path": "site/themes/template/layouts/_default/single.html",
    "content": "{{ define \"main\" }}\n\t<main>\n\t\t<article>\n\t\t\t<div class=\"hero subpage-hero\">\n\t\t\t\t<div class=\"wrapper\">\n\t\t\t\t\t<h1>Blog</h1>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div class=\"wrapper blog\">\n\t\t\t\t<div class=\"blog-post\">\n\t\t\t\t\t<h2>{{ .Title }}</h2>\n\t\t\t\t\t<p class=\"author\">\n\t\t\t\t\t\t<a href=\"/tags/{{ .Params.author | urlize }}\">{{ .Params.author }}</a>\n\t\t\t\t\t</p>\n\t\t\t\t\t<p class=\"date\">{{ dateFormat \"Jan 2, 2006\" .Date }}</p>\n\t\t\t\t\t{{ .Content }}\n\t\t\t\t</div>\n\t\t\t\t<h2>Related Content</h2>\n\t\t\t\t<div class=\"grid three\">\n\t\t\t\t\t{{ $related := (where (.Site.RegularPages.Related .) \"Type\" \"posts\") | first 3 }}\n\t\t\t\t\t{{ with $related }}\n\t\t\t\t\t\t{{ range . }}\n\t\t\t\t\t\t\t{{ partial \"blog-post-card.html\" . }}\n\t\t\t\t\t\t{{ end }}\n\t\t\t\t\t{{ end }}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</article>\n\t</main>\n{{ end }}\n\n\n\n"
  },
  {
    "path": "site/themes/template/layouts/_default/summary.html",
    "content": "<article>\n\t<h1><a href=\"{{ .Permalink }}\">{{ .Title }}</a></h1>\n\t<time>{{ .Date.Format \"02.01.2006 15:04\" }}</time>\n\t{{ range .Params.tags }}\n\t<a href=\"{{ \"/tags/\" | relLangURL }}{{ . | urlize }}\">{{ . }}</a>\n\t{{ end }}\n\t<div>\n\t\t{{ .Summary }}\n\t\t{{ if .Truncated }}\n\t\t\t<a href=\"{{ .Permalink }}\">Read more...</a>\n\t\t{{ end }}\n\t</div>\n</article>\n"
  },
  {
    "path": "site/themes/template/layouts/_default/tag.html",
    "content": "{{ define \"main\" }}\n    <main>\n        <div class=\"hero subpage-hero blog\">\n            <div class=\"wrapper\">\n                <h1>Blog Posts by {{ .Title }}</h1>\n            </div>\n        </div>\n        <div class=\"wrapper blog landing\">\n            <div class=\"grid three\">\n                {{ range .Pages.ByDate }}\n\t\t\t\t\t{{ partial \"blog-post-card.html\" . }}\n\t\t\t\t{{ end }}\n            </div>\n        </div>\n    </main>\n{{ end }}"
  },
  {
    "path": "site/themes/template/layouts/_default/versions.html",
    "content": "{{ if .Site.Params.Use_advanced_docs }}\n    <div class=\"dropdown\">\n        {{ if .Site.Params.docs_versioning }}\n            <button class=\"btn btn-primary dropdown-toggle\" type=\"button\" id=\"dropdownMenuButton\" data-toggle=\"dropdown\"\n                    aria-haspopup=\"true\" aria-expanded=\"false\" onclick=\"docsVersionToggle()\">\n                {{ .CurrentSection.Params.version }}\n            </button>\n            <div class=\"dropdown-menu\" id=\"dropdown-menu\" aria-labelledby=\"dropdownMenuButton\">\n                {{ $original_version := printf \"/%s/\" .CurrentSection.Params.version }}\n                {{ $latest_url := replace .Params.url .CurrentSection.Params.version .Site.Params.docs_latest | relURL }}\n                {{ $currentUrl := .Permalink }}\n                {{ range .Site.Params.docs_versions }}\n                    {{ $new_version := printf \"/%s/\" . }}\n                    <a class=\"dropdown-item\"\n                        href=\"{{ replace $currentUrl $original_version $new_version | relURL }}\">{{ . }}</a>\n                {{ end }}\n            </div>\n        {{ else }}\n            <span>{{ .Site.Params.Docs_latest }}</span>\n        {{ end }}\n    </div>\n{{ end }}"
  },
  {
    "path": "site/themes/template/layouts/index.html",
    "content": "{{ define \"main\" }}\n\t<main>\n\t\t{{ partial \"hero.html\" . }}\n\t\t{{ partial \"homepage-grid.html\" . }}\n\t\t<div class=\"bg-grey introduction\">\n\t\t\t<div class=\"wrapper grid two\">\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<p class=\"strong\"><a href='https://github.com/bitnami-labs/sealed-secrets/blob/main/README.md'>Learn More About Sealed Secrets</a></p>\n\t\t\t\t\t<p>Learn more about Sealed Secrets and how to create secure Secrets in Kubernetes</p>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<p class=\"strong\"><a href='https://github.com/bitnami-labs/sealed-secrets/blob/main/docs/developer/crypto.md#cryptographic-documentation'>Advanced Cryptography with Sealed Secrets</a></p>\n\t\t\t\t\t<p>How to apply the best possible encryption to your Sealed Secrets, from using customized Certificates to post-quantum recomendations.</p>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t\t{{ partial \"use-cases.html\" . }}\n\t\t{{ partial \"contributors.html\" . }}\n\t</main>\n{{ end }}\n"
  },
  {
    "path": "site/themes/template/layouts/index.redirects",
    "content": "{{ $latest := (cond (.Site.Params.docs_versioning) .Site.Params.docs_latest \"\") }}\n/docs                          /docs/{{ $latest }}     301!\n/docs/latest                   /docs/{{ $latest }}\n/docs/latest/*                 /docs/{{ $latest }}/:splat"
  },
  {
    "path": "site/themes/template/layouts/partials/blog-post-card.html",
    "content": "<div class=\"col\">\n    <div class=\"icon\">\n        <img src=\"{{ .Params.Image }}\" alt=\"{{ .Title }}\" />\n    </div>\n    <div class=\"content\">\n        <h3><a href=\"{{ .RelPermalink }}\">{{ .Title }}</a></h3>\n        <p>{{ .Params.Excerpt }}</p>\n    </div>\n</div>"
  },
  {
    "path": "site/themes/template/layouts/partials/contributors.html",
    "content": "<div class=\"team\">\n  <div class=\"wrapper\">\n      <h2>Meet the Sealed Secrets team:</h2>\n      <div class=\"grid three\">\n        {{ $contributors := .Site.GetPage \"/contributors\" }}\n        {{ range $contributors.Resources }}\n          <div class=\"bio\">\n              <div class=\"image avatar\"><img class=\"rounded-circle\" src=\"{{ .Params.image }}\" alt=\"{{ .Params.first_name }} {{ .Params.last_name }}\" /></div>\n              <div class=\"info\">\n                  <p class=\"name\"><a href=\"https://github.com/{{ .Params.github_handle }}\">{{ .Params.first_name }} {{ .Params.last_name }}</a></p>\n                  <p class=\"position\">{{ .Content }}</p>\n              </div>\n          </div>\n          {{ end }}\n      </div>\n      <h3>Contributing</h3>\n      <p>\n        Sealed Secrets is released as open-source software and provides community\n        support through our GitHub project page. If you encounter an issue or have\n        a question, feel free to reach out on the\n        <a\n          target=\"_blank\"\n          rel=\"noopener\"\n          href=\"{{$.Site.Params.github_base_url}}/issues/new\">GitHub issues page for Sealed Secrets</a>.\n      </p>\n      <p>\n        The Sealed Secrets project team welcomes contributions from the community —\n        please have a look at our\n        <a href=\"https://github.com/bitnami-labs/sealed-secrets/blob/main/CONTRIBUTING.md\">contributing documentation</a>.\n      </p>\n  </div>\n</div>"
  },
  {
    "path": "site/themes/template/layouts/partials/docs-right-bar.html",
    "content": "{{ if .Site.Params.docs_right_sidebar }}\n<div class=\"right-nav\" id=\"right-nav\">\n    <div class=\"right-nav-content\">\n        <ul class=\"buttons\">\n            {{ if (or .IsNode .IsPage) }}\n                {{ $issueBody := printf \"**On Page:** [%s](%s)\" .Title .Permalink | htmlEscape }}\n                {{ $issueQuery := (querify \"body\" $issueBody) }}\n                <li><a href=\"{{ $.Site.Params.github_base_url }}/issues/new?{{ $issueQuery | safeURL }}\" target=\"_blank\">Report Issues</a></li>\n                {{ $editQuery := (querify \"description\" \"Signed-off-by: NAME <EMAIL_ADDRESS>\\n\\n\") }}\n                <li><a href=\"{{ $.Site.Params.github_base_url }}/edit/main/content/{{ with .File }}{{ .Path }}{{ end }}?{{ $editQuery | safeURL }}\" target=\"_blank\"><img src=\"/img/github-blue.svg\" /> Edit</a></li>\n            {{ end }}\n        </ul>\n        {{ if ne .TableOfContents \"<nav id=\\\"TableOfContents\\\"></nav>\" }}\n            <h4 class=\"strong\">On this page:</h4>\n            {{ .TableOfContents }}\n        {{ end }}\n    </div>\n</div>\n{{ end }}\n"
  },
  {
    "path": "site/themes/template/layouts/partials/docs-sidebar.html",
    "content": "<div class=\"side-nav\">\n    {{ if .Site.Params.use_advanced_docs }}\n        <noscript>Please enable javascript to use dropdown navigation</noscript>\n        {{ $version := .CurrentSection.Params.version }}\n        {{ .Render \"versions\" }}\n        {{ .Render \"search\" }}\n        {{ if $version }}\n            {{ $tocTemplateName := index (index $.Site.Data.docs \"toc-mapping\") $version }}\n            {{ if not $tocTemplateName }}\n                {{ $tocTemplateName = \"default\" }}\n            {{ end }}\n            {{ $toc := (index $.Site.Data \"docs\" $tocTemplateName).toc }}\n            {{ range $toc }}\n                <h4>{{ .title }}</h4>\n                <ul>\n                    {{ range .subfolderitems }}\n                    <li>\n                        {{ $url :=  (index (print \"/docs/\" $version .url \"/\"))  }}\n                        <a href=\"{{ $url }}\" {{ if (eq  $.Page.RelPermalink $url)  }}class=\"active\"{{ end }}>{{ .page }}</a>\n                    </li>\n                    {{ end }}\n                </ul>\n            {{ end }}\n        {{ end }}\n    {{ else }}\n        <ul>\n            {{ $currentPage := . }}\n            {{ range .Site.Menus.docs }}\n                <li><a href=\"{{ .URL }}\" {{ if (eq $currentPage.RelPermalink .URL)  }}class=\"active\"{{ end }}>{{ .Name }}</a></li>\n            {{ end }}\n        </ul>\n    {{ end }}\n</div>"
  },
  {
    "path": "site/themes/template/layouts/partials/footer.html",
    "content": "<footer>\n\t<div class=\"wrapper footer-links\">\n\t\t<div class=\"top-links\">\n\t\t\t<ul class=\"left-links\">\n\t\t\t\t<li><a href=\"{{ .Site.Params.twitter_url }}\"><img src=\"/img/twitter.png\" alt=\"Twitter logo\" />Twitter</a></li>\n\t\t\t\t<li><a href=\"{{ .Site.Params.github_url }}\"><img src=\"/img/github.svg\" alt=\"Github logo\" />Github</a></li>\n\t\t\t\t<li><a href=\"{{ .Site.Params.slack_url }}\"><img src=\"/img/slack.png\" alt=\"Slack logo\" /><span class=\"desktop\">#Slack</span><span class=\"mobile\">Slack</span></a></li>\n\t\t\t</ul>\n\t\t</div>\n\t\t<div class=\"bottom-links\">\n\t\t  <p>\n\t\t\t<span>\n\t\t\t  Copyright &copy; 2005-{{ now.Year }} Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries.\n\t\t\t</span>\n\t\t\t<span>\n\t\t\t  <a target=\"_blank\" rel=\"noopener\" title=\"VMware by Broadcom. This link will open in a new tab\"\n\t\t\t\thref=\"https://github.com/vmware/\">\n\t\t\t\t<img class=\"footer-logo\" src=\"/img/vmware-broadcom-logo.svg\" alt=\"VMware by Broadcom logo\" />\n\t\t\t  </a>\n\t\t\t</span>\n\t\t\t<span>\n\t\t\t  <a target=\"_blank\" rel=\"noopener\" title=\"This link will open in a new tab\" href=\"https://www.vmware.com/help/legal.html\">Terms of Use</a> |\n\t\t\t  <a target=\"_blank\" rel=\"noopener\" title=\"This link will open in a new tab\" href=\"https://www.vmware.com/help/privacy.html\">Privacy Policy</a> |\n\t\t\t  <a target=\"_blank\" rel=\"noopener\" title=\"This link will open in a new tab\" href=\"https://www.vmware.com/help/privacy/california-privacy-rights.html\">Your California Privacy Rights</a>\n\t\t\t</span>\n\t\t  </p>\n\t\t</div>\n\t</div>\n</footer>\n"
  },
  {
    "path": "site/themes/template/layouts/partials/getting-started.html",
    "content": "{{ $latest := (cond (.Site.Params.docs_versioning) .Site.Params.docs_latest \"\") }}\n<div class=\"getting-started\">\n\t<div class=\"wrapper clearfix\">\n\t\t<div class=\"left-side\">\n\t\t\t<h2>Getting started</h2>\n      <p>\n        Discover how to deploy Sealed Secrets in your cluster, and start managing your Kubernetes Secrets in a secure way!\n      </p>\n    </div>\n    <div class=\"right-side\">\n      <a href=\"https://github.com/bitnami-labs/sealed-secrets#readme\" class=\"button\">Read the docs</a>\n    </div>\n\n\t</div>\n</div>"
  },
  {
    "path": "site/themes/template/layouts/partials/header.html",
    "content": "{{ $latest := (cond (.Site.Params.docs_versioning) .Site.Params.docs_latest \"\") }}\n<header>\n\t<div class=\"wrapper\">\n\t\t<a href=\"{{ .Site.BaseURL }}\"><img class=\"image\" src=\"/img/sealedsecrets-logo.svg\" alt=\"Logo\" /></a>\n\t\t<ul class=\"desktop-links\">\n\t\t\t<li><a href=\"/\" {{ if (eq .RelPermalink \"/\")  }}class=\"active\"{{ end }}>Home</a></li>\n\t\t\t<li><a href=\"/community/\" {{ if (eq .RelPermalink \"/community/\")  }}class=\"active\"{{ end }}>Community</a></li>\n\t\t\t<li><a href=\"/resources/\" {{ if (eq .RelPermalink \"/resources/\")  }}class=\"active\"{{ end }}>Resources</a></li>\n\t\t\t<!--li><a href=\"/blog/\" {{ if or (eq .Page.Section \"posts\") (eq .Page.Section \"tags\") }}class=\"active\"{{ end }}>Blog</a></li>\n\t\t\t<li><a href=\"/docs/{{ $latest }}\" {{ if (eq .Page.Section \"docs\") }}class=\"active\"{{ end }}>Documentation</a></li-->\n\t\t</ul>\n\t\t<button type=\"button\" class=\"mobile\" onclick=\"mobileNavToggle()\">\n\t\t\t<img class=\"collapsed-icon\" src=\"/img/hamburger.svg\" alt=\"Mobile nav icon\">\n\t\t\t<img class=\"expanded-icon\" src=\"/img/close.svg\" alt=\"Mobile nav icon\">\n\t\t</button>\n\t\t<div id=\"mobile-menu\" class=\"mobile-menu mobile\">\n\t\t\t<ul class=\"header-links\">\n\t\t\t\t<li><a href=\"/\" {{ if (eq .RelPermalink \"/\")  }}class=\"active\"{{ end }}>Home</a></li>\n\t\t\t\t<li><a href=\"/community/\" {{ if (eq .RelPermalink \"/community/\")  }}class=\"active\"{{ end }}>Community</a></li>\n\t\t\t\t<li><a href=\"/resources/\" {{ if (eq .RelPermalink \"/resources/\")  }}class=\"active\"{{ end }}>Resources</a></li>\n\t\t\t\t<!--li><a href=\"/blog/\" {{ if or (eq .Page.Section \"posts\") (eq .Page.Section \"tags\") }}class=\"active\"{{ end }}>Blog</a></li>\n\t\t\t\t<li><a href=\"/docs/{{ $latest }}\" {{ if (eq .Page.Section \"docs\") }}class=\"active\"{{ end }}>Documentation</a></li-->\n\t\t\t</ul>\n\t\t\t<div class=\"social\">\n\t\t\t\t<a href=\"#\"><img src=\"/img/twitter.png\" />Twitter</a>\n\t\t\t\t<a href=\"#\"><img src=\"/img/github.svg\" />GitHub</a>\n\t\t\t\t<a href=\"#\"><img src=\"/img/slack.png\" />Slack</a>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</header>"
  },
  {
    "path": "site/themes/template/layouts/partials/hero.html",
    "content": "<div class=\"hero homepage\">\n\t<div class=\"wrapper\">\n\t\t<div class=\"text-block\">\n\t\t\t<h1>\"Sealed Secrets\" for Kubernetes</h1>\n\t\t\t<p>Sealed Secrets provides declarative Kubernetes Secret Management in a secure way. Since the Sealed Secrets are encrypted, they can be safely stored in a code repository. This enables an easy to implement GitOps flow that is very popular among the OSS community.\n\t\t\t</p>\n\t\t\t<div class=\"buttons\">\n                <a class=\"button\" href=\"https://github.com/bitnami-labs/sealed-secrets#readme\">LEARN MORE</a>\n\t\t\t\t<a class=\"button secondary\" href=\"https://www.github.com/bitnami-labs/sealed-secrets/releases/latest\">DOWNLOAD SEALED SECRETS</a>\n            </div>\n\t\t</div>\n\t</div>\n</div>"
  },
  {
    "path": "site/themes/template/layouts/partials/homepage-grid.html",
    "content": "<div class=\"grid-container\">\n\t<div class=\"wrapper\">\n\t\t<div class=\"grid three\">\n\t\t\t<div class=\"card\">\n\t\t\t\t<img src=\"/img/simple.svg\" class=\".filter-blue\"/>\n\t\t\t\t<h3>On your command line</h3>\n\t\t\t\t<p>Sealed Secrets offers a powerful CLI tool (kubeseal) to one-way encrypt your Kubernetes Secret easily.</p>\n\t\t\t</div>\n\t\t\t<div class=\"card\">\n\t\t\t\t<img src=\"/img/kubernetes.svg\" height=\"66\" class=\"filter-blue\" />\n\t\t\t\t<h3>On your K8S cluster</h3>\n\t\t\t\t<p>The Sealed Secrets controller will decrypt any Sealed Secret into its equivalent Kubernetes Secret</p>\n\t\t\t</div>\n\t\t\t<div class=\"card\">\n\t\t\t\t<img src=\"/img/storagesecure.svg\" height=\"66\" class=\"filter-blue\" />\n\t\t\t\t<h3>On your code repository</h3>\n\t\t\t\t<p>Sealed Secrets are safe to store in your local code repository, along with the rest of your configuration.</p>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</div>"
  },
  {
    "path": "site/themes/template/layouts/partials/pagination.html",
    "content": "{{ $paginator := .Paginator }}\n{{ if gt $paginator.TotalPages 1 }}\n<div class=\"pagination\">\n    <ul>\n        {{ if $paginator.HasPrev }}\n            <li class=\"left-arrow\"><a href=\"{{ $paginator.Prev.URL }}\"><img src=\"/img/left-arrow.svg\" /></a></li>\n        {{ end }}\n        {{ range $paginator.Pagers }}\n            <li > <a href=\"{{ .URL }}\" class=\"{{ if eq . $paginator }}active{{ end }}\">{{ .PageNumber }}</a></li>\n        {{ end }}\n        {{ if $paginator.HasNext }}\n            <li class=\"right-arrow\"><a href=\"{{ $paginator.Next.URL }}\"><img src=\"/img/right-arrow.svg\" /></a></li>\n        {{ end }}\n    </ul>\n</div>\n{{ end }}"
  },
  {
    "path": "site/themes/template/layouts/partials/use-cases.html",
    "content": "<div class=\"wrapper use-cases\">\n    <h2>Features</h2>\n    <div class=\"grid two\">\n        <div class=\"col image\">\n            <img src=\"/img/administration.svg\" />\n        </div>\n        <div class=\"col text\">\n            <p class=\"strong\">One-way Encryption</p>\n            <p>SealedSecrets are a \"write only\" device. The idea is that the SealedSecret can be decrypted only by the controller running in the target cluster and nobody else (not even the original author) is able to obtain the original Secret from the SealedSecret.</p>\n            <p><a href=\"https://github.com/bitnami-labs/sealed-secrets#readme\" class=\"button tertiary\">Learn more</a></p>\n        </div>\n    </div>\n    <div class=\"grid two image-right\">\n        <div class=\"col text\">\n            <p class=\"strong\">Sealing key renewal</p>\n            <p>Sealing keys are automatically renewed every 30 days. Which means a new sealing key is created and appended to the set of active sealing keys the controller can use to unseal Sealed Secret resources.</p>\n            <p><a href=\"https://github.com/bitnami-labs/sealed-secrets#sealing-key-renewal\" class=\"button tertiary\">Learn more</a></p>\n        </div>\n        <div class=\"col image\">\n            <img src=\"/img/authentication.svg\" />\n        </div>\n    </div>\n    <div class=\"grid two\">\n        <div class=\"col image\">\n            <img src=\"/img/security.svg\" />\n        </div>\n        <div class=\"col text\">\n            <p class=\"strong\">Sealed Secrets Metrics</p>\n            <p>The Sealed Secrets Controller running in Kubernetes exposes Prometheus metrics. These metrics enable operators to observe how it is performing. For example how many SealedSecret unseals have been attempted and how many errors may have occured due to RBAC permissions, wrong key, corrupted data, etc.</p>\n            <p><a href=\"https://github.com/bitnami-labs/sealed-secrets/tree/main/contrib/prometheus-mixin#readme\" class=\"button tertiary\">Learn more</a></p>\n        </div>\n    </div>\n</div>"
  },
  {
    "path": "site/themes/template/layouts/shortcodes/readfile.html",
    "content": "{{ .Get \"file\" | readFile | safeHTML }}\n"
  },
  {
    "path": "site/themes/template/static/fonts/Open Font License.md",
    "content": "\tCopyright (c) 2015, Chris Simpson <chris@victoryonemedia.com>, with Reserved Font Name: \"Metropolis\".\n\n\tThis Font Software is licensed under the SIL Open Font License, Version 1.1.\n\tThis license is copied below, and is also available with a FAQ at:\n\thttp://scripts.sil.org/OFL\n\n\tVersion 2.0 - 18 March 2012\n\n\nSIL Open Font License\n====================================================\n\n\nPreamble\n----------\n\nThe goals of the Open Font License (OFL) are to stimulate worldwide\ndevelopment of collaborative font projects, to support the font creation\nefforts of academic and linguistic communities, and to provide a free and\nopen framework in which fonts may be shared and improved in partnership\nwith others.\n\nThe OFL allows the licensed fonts to be used, studied, modified and\nredistributed freely as long as they are not sold by themselves. The\nfonts, including any derivative works, can be bundled, embedded, \nredistributed and/or sold with any software provided that any reserved\nnames are not used by derivative works. The fonts and derivatives,\nhowever, cannot be released under any other type of license. The\nrequirement for fonts to remain under this license does not apply\nto any document created using the fonts or their derivatives.\n\nDefinitions\n-------------\n\n`\"Font Software\"` refers to the set of files released by the Copyright\nHolder(s) under this license and clearly marked as such. This may\ninclude source files, build scripts and documentation.\n\n`\"Reserved Font Name\"` refers to any names specified as such after the\ncopyright statement(s).\n\n`\"Original Version\"` refers to the collection of Font Software components as\ndistributed by the Copyright Holder(s).\n\n`\"Modified Version\"` refers to any derivative made by adding to, deleting,\nor substituting -- in part or in whole -- any of the components of the\nOriginal Version, by changing formats or by porting the Font Software to a\nnew environment.\n\n`\"Author\"` refers to any designer, engineer, programmer, technical\nwriter or other person who contributed to the Font Software.\n\nPermission & Conditions\n------------------------\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of the Font Software, to use, study, copy, merge, embed, modify,\nredistribute, and sell modified and unmodified copies of the Font\nSoftware, subject to the following conditions:\n\n1. Neither the Font Software nor any of its individual components,\nin Original or Modified Versions, may be sold by itself.\n\n2. Original or Modified Versions of the Font Software may be bundled,\nredistributed and/or sold with any software, provided that each copy\ncontains the above copyright notice and this license. These can be\nincluded either as stand-alone text files, human-readable headers or\nin the appropriate machine-readable metadata fields within text or\nbinary files as long as those fields can be easily viewed by the user.\n\n3. No Modified Version of the Font Software may use the Reserved Font\nName(s) unless explicit written permission is granted by the corresponding\nCopyright Holder. This restriction only applies to the primary font name as\npresented to the users.\n\n4. The name(s) of the Copyright Holder(s) or the Author(s) of the Font\nSoftware shall not be used to promote, endorse or advertise any\nModified Version, except to acknowledge the contribution(s) of the\nCopyright Holder(s) and the Author(s) or with their explicit written\npermission.\n\n5. The Font Software, modified or unmodified, in part or in whole,\nmust be distributed entirely under this license, and must not be\ndistributed under any other license. The requirement for fonts to\nremain under this license does not apply to any document created\nusing the Font Software.\n\nTermination\n-----------\n\nThis license becomes null and void if any of the above conditions are\nnot met.\n\n\n\tDISCLAIMER\n\n\tTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n\tEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF\n\tMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT\n\tOF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE\n\tCOPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n\tINCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL\n\tDAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n\tFROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM\n\tOTHER DEALINGS IN THE FONT SOFTWARE.\n"
  },
  {
    "path": "site/themes/template/static/fonts/README.md",
    "content": "# The Metropolis Typeface\n\nThe Vision\n---\nTo create a modern, geometric typeface. Open sourced, and openly available. Influenced by other popular geometric, minimalist sans-serif typefaces of the new millenium. Designed for optimal readability at small point sizes while beautiful at large point sizes.\n\nDecember 2017 update\n---\nCurrently working on greatly improving spacing and kerning of the base typeface. Once this is done, work on other variations (e.g. rounded or slab) can begin in earnest.\n\nThe License\n---\nLicensed under Open Font License (OFL). Available to anyone and everyone. Contributions welcome.\n\nContact\n---\nContact me via chris.m.simpson@icloud.com or http://twitter.com/ChrisMSimpson for any questions, requests or improvements (or just submit a pull request).\n\nSupport\n---\nYou can now support work on Metropolis via Patreon at https://www.patreon.com/metropolis.\n\n"
  },
  {
    "path": "site/themes/template/static/js/main.js",
    "content": "\"use strict\";\n\nfunction mobileNavToggle() {\n    var menu = document.getElementById('mobile-menu').parentElement;\n    menu.classList.toggle('mobile-menu-visible');\n}\n\nfunction docsVersionToggle() {\n    var menu = document.getElementById('dropdown-menu');\n    menu.classList.toggle('dropdown-menu-visible');\n}\n\nwindow.onclick = function(event) {\n    var \n        target = event.target,\n        menu = document.getElementById('dropdown-menu')\n    ;\n\n    if(!target.classList.contains('dropdown-toggle')) {\n        menu.classList.remove('dropdown-menu-visible');\n    }\n}"
  },
  {
    "path": "vendor_jsonnet/kube-libsonnet/.travis.yml",
    "content": "language: bash\n\nos:\n  - linux\n\nservices:\n  - docker\n\nbefore_install: # update to docker-ce\n  - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -\n  - sudo add-apt-repository \"deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) edge\"\n  - sudo apt-get update\n  - sudo apt-get -y install docker-ce\n\nscript:\n  - make tests\n"
  },
  {
    "path": "vendor_jsonnet/kube-libsonnet/CODEOWNERS",
    "content": "*  @dbarranco @jbianquetti-nami @jjo\n"
  },
  {
    "path": "vendor_jsonnet/kube-libsonnet/LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\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"
  },
  {
    "path": "vendor_jsonnet/kube-libsonnet/Makefile",
    "content": "# Originally taken from https://github.com/bitnami-labs/kube-manifests/,\n# trimmed down to only run lib testing.\n#\n# Provides 'test' target.  Uses docker.\nall:\n\t@echo make tests\n\ntests:\n\tmake -C tests\n\n.PHONY: all tests\n"
  },
  {
    "path": "vendor_jsonnet/kube-libsonnet/README.md",
    "content": "# kube-libsonnet\n\nThis repo has been originally populated by the `lib/` folder contents\nfrom `https://github.com/bitnami-labs/kube-manifests` as of Mar/2018,\naiming to provide a library of `jsonnet` manifests for common\nKubernetes objects (such as `Deployment`, `Service`, `Ingress`, etc).\n\nAccordingly, above `kube-manifests` has been changed to use this repo as\na git submodule, i.e.:\n\n    $ git submodule add https://github.com/bitnami-labs/kube-libsonnet\n    $ cat .gitmodules\n    [submodule \"lib\"]\n    path = lib\n    url = https://github.com/bitnami-labs/kube-libsonnet\n\n## Testing\n\nUnit and e2e-ish testing at tests/, needs usable `docker-compose`\nat node, will run a `k3s` \"dummy\" container to serve Kube API, enough\nto for `kubecfg validate` against it:\n\n    make tests\n\nIf you don't want that full kube-api stack (will then use your \"local\"\nkubernetes configured environment), you can run:\n\n    make -C tests test-srcs test-kube\n"
  },
  {
    "path": "vendor_jsonnet/kube-libsonnet/bitnami.libsonnet",
    "content": "// Generic stuff is in kube.libsonnet - this file contains\n// bitnami-specific conventions.\n\nlocal kube = import \"kube.libsonnet\";\n\nlocal perCloudSvcAnnotations(cloud, internal, service) = (\n  {\n    aws: {\n      \"service.beta.kubernetes.io/aws-load-balancer-connection-draining-enabled\": \"true\",\n      \"service.beta.kubernetes.io/aws-load-balancer-connection-draining-timeout\": std.toString(service.target_pod.spec.terminationGracePeriodSeconds),\n      // Use PROXY protocol (nginx supports this too)\n      \"service.beta.kubernetes.io/aws-load-balancer-proxy-protocol\": \"*\",\n      // Does LB do NAT or DSR? (OnlyLocal implies DSR)\n      // https://kubernetes.io/docs/tutorials/services/source-ip/\n      // NB: Don't enable this without modifying set-real-ip-from above!\n      // Not supported on aws in k8s 1.5 - immediate close / serves 503s.\n      //\"service.beta.kubernetes.io/external-traffic\": \"OnlyLocal\",\n    },\n    gke: {},\n  }[cloud] + if internal then {\n    aws: {\n      \"service.beta.kubernetes.io/aws-load-balancer-internal\": \"0.0.0.0/0\",\n    },\n    gke: {\n      \"cloud.google.com/load-balancer-type\": \"internal\",\n    },\n  }[cloud] else {}\n);\n\nlocal perCloudSvcSpec(cloud) = (\n  {\n    aws: {},\n    // Required to get real src IP address, which also allows proper\n    // ingress.kubernetes.io/whitelist-source-range matching\n    gke: { externalTrafficPolicy: \"Local\" },\n  }[cloud]\n);\n\n{\n  ElbService(name, cloud, internal): kube.Service(name) {\n    local service = self,\n\n    metadata+: {\n      annotations+: perCloudSvcAnnotations(cloud, internal, service),\n    },\n    spec+: { type: \"LoadBalancer\" } + perCloudSvcSpec(cloud),\n  },\n\n  Ingress(name): kube.Ingress(name) {\n    local ing = self,\n\n    host:: error \"host required\",\n    target_svc:: error \"target_svc required\",\n    // Default to single-service - override if you want something else.\n    paths:: [{ path: \"/\", backend: ing.target_svc.name_port }],\n    secretName:: \"%s-cert\" % [ing.metadata.name],\n    // cert_provider can either be:\n    // - \"kcm\": DEPRECATED (will be removed in T26526) uses old kube-cert-manager via route53 for ACME dns-01 challenge\n    // - \"cm-dns\": cert-manager using route53 for ACME dns-01 challenge\n    // - \"cm-http\": cert-manager using ACME http, requires public ingress (kube-lego already replaced by cert-manager)\n    cert_provider:: \"cm-dns\",\n\n    kcm_metadata:: {\n      annotations+: {\n        \"stable.k8s.psg.io/kcm.provider\": \"route53\",\n        \"stable.k8s.psg.io/kcm.email\": \"sre@bitnami.com\",\n      },\n      labels+: {\n        \"stable.k8s.psg.io/kcm.class\": \"default\",\n      },\n    },\n    cm_dns_metadata:: {\n      annotations+: {\n        \"certmanager.k8s.io/cluster-issuer\": \"letsencrypt-prod-dns\",\n        \"certmanager.k8s.io/acme-challenge-type\": \"dns01\",\n        \"certmanager.k8s.io/acme-dns01-provider\": \"default\",\n      },\n    },\n    cm_http_metadata:: {\n      annotations+: {\n        \"certmanager.k8s.io/cluster-issuer\": \"letsencrypt-prod-http\",\n      },\n    },\n\n    metadata+: {\n      kcm: ing.kcm_metadata,\n      \"cm-dns\": ing.cm_dns_metadata,\n      \"cm-http\": ing.cm_http_metadata,\n    }[ing.cert_provider],\n    spec+: {\n      tls: [\n        {\n          hosts: std.set([r.host for r in ing.spec.rules]),\n          secretName: ing.secretName,\n        },\n      ],\n\n      rules: [\n        {\n          host: ing.host,\n          http: {\n            paths: ing.paths,\n          },\n        },\n      ],\n    },\n  },\n\n  PromScrape(port): {\n    local scrape = self,\n    prom_path:: \"/metrics\",\n\n    metadata+: {\n      annotations+: {\n        \"prometheus.io/scrape\": \"true\",\n        \"prometheus.io/port\": std.toString(port),\n        \"prometheus.io/path\": scrape.prom_path,\n      },\n    },\n  },\n\n  PodZoneAntiAffinityAnnotation(pod): {\n    podAntiAffinity: {\n      preferredDuringSchedulingIgnoredDuringExecution: [\n        {\n          weight: 50,\n          podAffinityTerm: {\n            labelSelector: { matchLabels: pod.metadata.labels },\n            topologyKey: \"failure-domain.beta.kubernetes.io/zone\",\n          },\n        },\n        {\n          weight: 100,\n          podAffinityTerm: {\n            labelSelector: { matchLabels: pod.metadata.labels },\n            topologyKey: \"kubernetes.io/hostname\",\n          },\n        },\n      ],\n    },\n  },\n}\n"
  },
  {
    "path": "vendor_jsonnet/kube-libsonnet/examples/guestbook/guestbook.jsonnet",
    "content": "// Copyright 2017 The kubecfg authors\n//\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// Simple to demonstrate kubecfg using kube-libsonnet\n// This should not necessarily be considered a model jsonnet example\n// to build upon.\n\n// This is a simple port to jsonnet of the standard guestbook example\n// https://github.com/kubernetes/kubernetes/tree/master/examples/guestbook\n//\n// ```\n// kubecfg update guestbook.jsonnet\n//\n// # poke at \n// - $(minikube service frontend), etc\n// - kubectl proxy # then visit http://localhost:8001/api/v1/namespaces/default/services/frontend/proxy/ \n// kubecfg delete guestbook.jsonnet\n// ```\n\nlocal kube = import \"lib/kube.libsonnet\";\n\n{\n  frontend_deployment: kube.Deployment(\"frontend\") {\n    spec+: {\n      local my_spec = self,\n      replicas: 3,\n      template+: {\n        spec+: {\n          containers_+: {\n            gb_fe: kube.Container(\"gb-frontend\") {\n              image: \"gcr.io/google-samples/gb-frontend:v4\",\n              resources: { requests: { cpu: \"100m\", memory: \"100Mi\" } },\n              env_+: {\n                GET_HOSTS_FROM: \"dns\",\n                NUMBER_REPLICAS: my_spec.replicas,\n              },\n              ports_+: { http: { containerPort: 80 } },\n  }}}}}},\n\n  frontend_service: kube.Service(\"frontend\") {\n    target_pod: $.frontend_deployment.spec.template,\n    // spec+: { type: \"LoadBalancer\" },\n  },\n\n  redis_master_deployment: kube.Deployment(\"redis-master\") {\n    spec+: {\n      template+: {\n        spec+: {\n          containers_+: {\n            redis_master: kube.Container(\"redis-master\") {\n              image: \"gcr.io/google_containers/redis:e2e\",\n              resources: { requests: { cpu: \"100m\", memory: \"100Mi\" } },\n              ports_+: {\n                redis: { containerPort: 6379 },\n  }}}}}}},\n\n  redis_master_service: kube.Service(\"redis-master\") {\n    target_pod: $.redis_master_deployment.spec.template,\n  },\n\n  redis_slave_deployment: kube.Deployment(\"redis-slave\") {\n    spec+: {\n      replicas: 2,\n      template+: {\n        spec+: {\n          containers_+: {\n            redis_slave: kube.Container(\"redist-slave\") {\n              image: \"gcr.io/google_samples/gb-redisslave:v1\",\n              resources: {\n                requests: { cpu: \"100m\", memory: \"100Mi\" },\n              },\n              env_: {\n                GET_HOSTS_FROM: \"dns\",\n              },\n              ports_+: {\n                redis: { containerPort: 6379 },\n  }}}}}}},\n\n  redis_slave_service: kube.Service(\"redis-slave\") {\n    target_pod: $.redis_slave_deployment.spec.template,\n  },\n}\n"
  },
  {
    "path": "vendor_jsonnet/kube-libsonnet/examples/wordpress/backend.jsonnet",
    "content": "local kube = import \"lib/kube.libsonnet\";\n\nlocal labels = {\n  tier: \"backend\",\n};\n\n{\n  backend: {\n    secret: kube.Secret(\"mariadb\") {\n      metadata+: {\n        labels+: labels,\n      },\n      data_+: {\n        \"database_name\": \"webserver_db\",\n        \"database_user\": \"webserver_user\",\n        \"database_password\": \"webserver_db_password\",\n        \"replication_user\": \"replica_user\",\n        \"replication_password\": \"replica_password\",\n        \"root_user\": \"root_user\",\n        \"root_password\": \"root_password\"\n    }},\n\n    master: {\n      local masterLabels = labels + {\n        component: \"master\",\n      },\n      statefulset: kube.StatefulSet(\"mariadb-master\") {\n        metadata+: {\n          labels+: masterLabels,\n        },\n        spec+: {\n          template+: {\n            spec+: {\n              securityContext: {\n                runAsUser: 1001,\n                fsGroup: 1001,\n              },\n              containers_+: {\n                default: kube.Container(\"mariadb\") {\n                  image: \"bitnami/mariadb\",\n                  ports_+: { mysql: { containerPort: 3306 } },\n                  env_+: {\n                    MARIADB_REPLICATION_MODE: \"master\",\n                    MARIADB_REPLICATION_USER: kube.SecretKeyRef($.backend.secret, \"replication_user\"),\n                    MARIADB_REPLICATION_PASSWORD: kube.SecretKeyRef($.backend.secret, \"replication_password\"),\n                    MARIADB_ROOT_USER: kube.SecretKeyRef($.backend.secret, \"root_user\"),\n                    MARIADB_ROOT_PASSWORD: kube.SecretKeyRef($.backend.secret, \"root_password\"),\n                    MARIADB_USER: kube.SecretKeyRef($.backend.secret, \"database_user\"),\n                    MARIADB_DATABASE: kube.SecretKeyRef($.backend.secret, \"database_name\"),\n                    MARIADB_PASSWORD: kube.SecretKeyRef($.backend.secret, \"database_password\"),\n                  },\n                  livenessProbe: {\n                    initialDelaySeconds: 40,\n                    exec: {\n                      command: [\n                        \"sh\",\n                        \"-c\",\n                        \"exec mysqladmin status -u$MARIADB_ROOT_USER -p$MARIADB_ROOT_PASSWORD\",\n                  ]}},\n                  readinessProbe: self.livenessProbe {\n                    initialDelaySeconds: 30,\n                  },\n                  volumeMounts_+: {\n                    \"mariadb-data\": {\n                      \"mountPath\": \"/bitnami/mariadb\",\n                }}},\n                metrics: kube.Container(\"metrics\") {\n                  image: \"prom/mysqld-exporter:v0.10.0\",\n                  command: [\n                    \"sh\",\n                    \"-c\",\n                    \"DATA_SOURCE_NAME=\\\"$MARIADB_ROOT_USER:$MARIADB_ROOT_PASSWORD@(localhost:3306)/\\\" exec /bin/mysqld_exporter\",\n                  ],\n                  ports_+: { metrics: { containerPort: 9104 } },\n                  env_+: {\n                    MARIADB_ROOT_USER: kube.SecretKeyRef($.backend.secret, \"root_user\"),\n                    MARIADB_ROOT_PASSWORD: kube.SecretKeyRef($.backend.secret, \"root_password\"),\n                  },\n                  livenessProbe: {\n                    initialDelaySeconds: 15,\n                    timeoutSeconds: 1,\n                    httpGet: {\n                      path: \"/metrics\",\n                      port: 9104,\n                  }},\n                  readinessProbe: self.livenessProbe {\n                    initialDelaySeconds: 5,\n                    timeoutSeconds: 1,\n          }}}}},\n          volumeClaimTemplates_+: {\n            \"mariadb-data\": {\n              storage: \"10Gi\",\n              metadata+: {\n                labels+: masterLabels,\n      }}}}},\n      service: kube.Service(\"mariadb-master\") {\n        metadata+: {\n          labels+: masterLabels,\n          annotations+: {\n            \"prometheus.io/scrape\": \"true\",\n            \"prometheus.io/port\": \"9104\",\n        }},\n        target_pod: $.backend.master.statefulset.spec.template,\n        spec+: {\n          ports: [\n            {\n              name: \"mariadb\",\n              port: 3306,\n              targetPort: $.backend.master.statefulset.spec.template.spec.containers[0].ports[0].containerPort,\n            },\n            {\n              name: \"metrics\",\n              port: 9104,\n              targetPort: $.backend.master.statefulset.spec.template.spec.containers[1].ports[0].containerPort,\n    }]}}},\n\n    slave: {\n      local slaveLabels = labels + {\n        component: \"slave\",\n      },\n      statefulset: kube.StatefulSet(\"mariadb-slave\") {\n        metadata+: {\n          labels+: slaveLabels,\n        },\n        spec+: {\n          template+: {\n            spec+: {\n              securityContext: {\n                runAsUser: 1001,\n                fsGroup: 1001,\n              },\n              containers_+: {\n                default: kube.Container(\"mariadb\") {\n                  image: \"bitnami/mariadb\",\n                  ports_+: { mysql: { containerPort: 3306 } },\n                  env_+: {\n                    MARIADB_REPLICATION_MODE: \"slave\",\n                    MARIADB_REPLICATION_USER: kube.SecretKeyRef($.backend.secret, \"replication_user\"),\n                    MARIADB_REPLICATION_PASSWORD: kube.SecretKeyRef($.backend.secret, \"replication_password\"),\n                    MARIADB_MASTER_HOST: $.backend.master.service.metadata.name,\n                    MARIADB_MASTER_ROOT_USER: kube.SecretKeyRef($.backend.secret, \"root_user\"),\n                    MARIADB_MASTER_ROOT_PASSWORD: kube.SecretKeyRef($.backend.secret, \"root_password\"),\n                  },\n                  livenessProbe: {\n                    initialDelaySeconds: 40,\n                    exec: {\n                      command: [\n                        \"sh\",\n                        \"-c\",\n                        \"exec mysqladmin status -u$MARIADB_MASTER_ROOT_USER -p$MARIADB_MASTER_ROOT_PASSWORD\",\n                  ]}},\n                  readinessProbe: self.livenessProbe {\n                    initialDelaySeconds: 30,\n                  },\n                  volumeMounts_+: {\n                    \"mariadb-data\": {\n                      \"mountPath\": \"/bitnami/mariadb\",\n                }}},\n                metrics: kube.Container(\"metrics\") {\n                  image: \"prom/mysqld-exporter:v0.10.0\",\n                  command: [\n                    \"sh\",\n                    \"-c\",\n                    \"DATA_SOURCE_NAME=\\\"$MARIADB_MASTER_ROOT_USER:$MARIADB_MASTER_ROOT_PASSWORD@(localhost:3306)/\\\" exec /bin/mysqld_exporter\",\n                  ],\n                  ports_+: { metrics: { containerPort: 9104 } },\n                  env_+: {\n                    MARIADB_MASTER_ROOT_USER: kube.SecretKeyRef($.backend.secret, \"root_user\"),\n                    MARIADB_MASTER_ROOT_PASSWORD: kube.SecretKeyRef($.backend.secret, \"root_password\"),\n                  },\n                  livenessProbe: {\n                    initialDelaySeconds: 15,\n                    timeoutSeconds: 1,\n                    httpGet: {\n                      path: \"/metrics\",\n                      port: 9104,\n                  }},\n                  readinessProbe: self.livenessProbe {\n                    initialDelaySeconds: 5,\n                    timeoutSeconds: 5,\n          }}}}},\n          volumeClaimTemplates_+: {\n            \"mariadb-data\": {\n              storage: \"10Gi\",\n              metadata+: {\n                labels+: slaveLabels,\n      }}}}},\n      service: kube.Service(\"mariadb-slave\") {\n        metadata+: {\n          labels+: slaveLabels,\n          annotations+: {\n            \"prometheus.io/scrape\": \"true\",\n            \"prometheus.io/port\": \"9104\",\n        }},\n        target_pod: $.backend.slave.statefulset.spec.template,\n        spec+: {\n          ports: [\n            {\n              name: \"mariadb\",\n              port: 3306,\n              targetPort: $.backend.slave.statefulset.spec.template.spec.containers[0].ports[0].containerPort,\n            },\n            {\n              name: \"metrics\",\n              port: 9104,\n              targetPort: $.backend.slave.statefulset.spec.template.spec.containers[1].ports[0].containerPort,\n}]}}}}}\n"
  },
  {
    "path": "vendor_jsonnet/kube-libsonnet/examples/wordpress/frontend.jsonnet",
    "content": "local kube = import \"lib/kube.libsonnet\";\nlocal be = import \"backend.jsonnet\";\n\nlocal labels = {\n  tier: \"frontend\",\n};\n\n{\n  frontend: {\n    pvc: kube.PersistentVolumeClaim(\"wordpress\") {\n      metadata+: {\n        labels+: labels,\n      },\n      storage:: \"10Gi\",\n    },\n\n    configmap: kube.ConfigMap(\"wordpress\") {\n      metadata+: {\n        labels+: labels,\n      },\n      data: {\n        \"admin_first_name\": \"Admin\",\n        \"admin_last_name\": \"User\",\n        \"blog_name\": \"Kubernetes blog!\",\n    }},\n\n    secret: kube.Secret(\"wordpress\") {\n      metadata+: {\n        labels+: labels,\n      },\n      data_+: {\n        \"user\": \"user\",\n        \"password\": \"bitnami\",\n        \"mail\": \"user@example.com\",\n    }},\n\n    deployment: kube.Deployment(\"wordpress\") {\n      metadata+: {\n        labels+: labels,\n      },\n      spec+: {\n        template+: {\n          spec+: {\n            containers_+: {\n              default: kube.Container(\"wordpress\") {\n                image: \"bitnami/wordpress\",\n                ports_+: { http: { containerPort: 80 } },\n                env_+: {\n                  MARIADB_HOST: be.backend.master.service.metadata.name,\n                  WORDPRESS_DATABASE_USER: kube.SecretKeyRef(be.backend.secret, \"database_user\"),\n                  WORDPRESS_DATABASE_NAME: kube.SecretKeyRef(be.backend.secret, \"database_name\"),\n                  WORDPRESS_DATABASE_PASSWORD: kube.SecretKeyRef(be.backend.secret, \"database_password\"),\n                  WORDPRESS_USERNAME: kube.SecretKeyRef($.frontend.secret, \"user\"),\n                  WORDPRESS_EMAIL: kube.SecretKeyRef($.frontend.secret, \"mail\"),\n                  WORDPRESS_PASSWORD: kube.SecretKeyRef($.frontend.secret, \"password\"),\n                  WORDPRESS_BLOG_NAME: kube.ConfigMapRef($.frontend.configmap, \"blog_name\"),\n                  WORDPRESS_FIRST_NAME: kube.ConfigMapRef($.frontend.configmap, \"admin_first_name\"),\n                  WORDPRESS_LAST_NAME: kube.ConfigMapRef($.frontend.configmap, \"admin_last_name\"),\n                },\n                livenessProbe: {\n                  initialDelaySeconds: 120,\n                  httpGet:  {\n                    path: \"/wp-login.php\",\n                    port: 80\n                }},\n                readinessProbe: self.livenessProbe {\n                  initialDelaySeconds: 60,\n                },\n                volumeMounts_+: {\n                  \"wordpress-data\": {\n                    \"mountPath\": \"/bitnami\",\n          }}}},\n          volumes_+: {\n            \"wordpress-data\": {\n              \"persistentVolumeClaim\": {\n                \"claimName\": \"wordpress\",\n    }}}}}}},\n\n    service: kube.Service(\"wordpress\") {\n      metadata+: {\n        labels+: labels,\n      },\n      target_pod: $.frontend.deployment.spec.template,\n}}}\n"
  },
  {
    "path": "vendor_jsonnet/kube-libsonnet/examples/wordpress/wordpress.jsonnet",
    "content": "// Copyright (c) 2018 Bitnami\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// ```\n// kubecfg update wordpress.jsonnet\n//\n// kubecfg delete wordpress.jsonnet\n// ```\n\nlocal kube = import \"lib/kube.libsonnet\";\nlocal fe = import \"frontend.jsonnet\";\nlocal be = import \"backend.jsonnet\";\n\nlocal findObjs(top) = std.flattenArrays([\n  if (std.objectHas(v, \"apiVersion\") && std.objectHas(v, \"kind\")) then [v] else findObjs(v)\n  for v in kube.objectValues(top)\n]);\n\nkube.List() {\n  items_+: {\n    frontend: fe,\n    backend: be,\n  },\n  items: findObjs(self.items_),\n}\n"
  },
  {
    "path": "vendor_jsonnet/kube-libsonnet/kube.libsonnet",
    "content": "// Generic library of Kubernetes objects (https://github.com/bitnami-labs/kube-libsonnet)\n//\n// Objects in this file follow the regular Kubernetes API object\n// schema with two exceptions:\n//\n// ## Optional helpers\n//\n// A few objects have defaults or additional \"helper\" hidden\n// (double-colon) fields that will help with common situations.  For\n// example, `Service.target_pod` generates suitable `selector` and\n// `ports` blocks for the common case of a single-pod/single-port\n// service.  If for some reason you don't want the helper, just\n// provide explicit values for the regular Kubernetes fields that the\n// helper *would* have generated, and the helper logic will be\n// ignored.\n//\n// ## The Underscore Convention:\n//\n// Various constructs in the Kubernetes API use JSON arrays to\n// represent unordered sets or named key/value maps.  This is\n// particularly annoying with jsonnet since we want to use jsonnet's\n// powerful object merge operation with these constructs.\n//\n// To combat this, this library attempts to provide more \"jsonnet\n// native\" variants of these arrays in alternative hidden fields that\n// end with an underscore.  For example, the `env_` block in\n// `Container`:\n// ```\n// kube.Container(\"foo\") {\n//   env_: { FOO: \"bar\" },\n// }\n// ```\n// ... produces the expected `container.env` JSON array:\n// ```\n// {\n//   \"env\": [\n//     { \"name\": \"FOO\", \"value\": \"bar\" }\n//   ]\n// }\n// ```\n//\n// If you are confused by the underscore versions, or don't want them\n// in your situation then just ignore them and set the regular\n// non-underscore field as usual.\n//\n//\n// ## TODO\n//\n// TODO: Expand this to include all API objects.\n//\n// Should probably fill out all the defaults here too, so jsonnet can\n// reference them.  In addition, jsonnet validation is more useful\n// (client-side, and gives better line information).\n\n{\n  // resource contructors will use kinds/versions/fields compatible at least with version:\n  minKubeVersion: {\n    major: 1,\n    minor: 9,\n    version: \"%s.%s\" % [self.major, self.minor],\n  },\n\n  // Returns array of values from given object.  Does not include hidden fields.\n  objectValues(o):: [o[field] for field in std.objectFields(o)],\n\n  // Returns array of [key, value] pairs from given object.  Does not include hidden fields.\n  objectItems(o):: [[k, o[k]] for k in std.objectFields(o)],\n\n  // Replace all occurrences of `_` with `-`.\n  hyphenate(s):: std.join(\"-\", std.split(s, \"_\")),\n\n  // Convert an octal (as a string) to number,\n  parseOctal(s):: (\n    local len = std.length(s);\n    local leading = std.substr(s, 0, len - 1);\n    local last = std.parseInt(std.substr(s, len - 1, 1));\n    assert last < 8 : \"found '%s' digit >= 8\" % [last];\n    last + (if len > 1 then 8 * $.parseOctal(leading) else 0)\n  ),\n\n  // Convert {foo: {a: b}} to [{name: foo, a: b}]\n  mapToNamedList(o):: [{ name: $.hyphenate(n) } + o[n] for n in std.objectFields(o)],\n\n  // Return object containing only these fields elements\n  filterMapByFields(o, fields): { [field]: o[field] for field in std.setInter(std.objectFields(o), fields) },\n\n  // Convert from SI unit suffixes to regular number\n  siToNum(n):: (\n    local convert =\n      if std.endsWith(n, \"m\") then [1, 0.001]\n      else if std.endsWith(n, \"K\") then [1, 1e3]\n      else if std.endsWith(n, \"M\") then [1, 1e6]\n      else if std.endsWith(n, \"G\") then [1, 1e9]\n      else if std.endsWith(n, \"T\") then [1, 1e12]\n      else if std.endsWith(n, \"P\") then [1, 1e15]\n      else if std.endsWith(n, \"E\") then [1, 1e18]\n      else if std.endsWith(n, \"Ki\") then [2, std.pow(2, 10)]\n      else if std.endsWith(n, \"Mi\") then [2, std.pow(2, 20)]\n      else if std.endsWith(n, \"Gi\") then [2, std.pow(2, 30)]\n      else if std.endsWith(n, \"Ti\") then [2, std.pow(2, 40)]\n      else if std.endsWith(n, \"Pi\") then [2, std.pow(2, 50)]\n      else if std.endsWith(n, \"Ei\") then [2, std.pow(2, 60)]\n      else error \"Unknown numerical suffix in \" + n;\n    local n_len = std.length(n);\n    std.parseInt(std.substr(n, 0, n_len - convert[0])) * convert[1]\n  ),\n\n  local remap(v, start, end, newstart) =\n    if v >= start && v <= end then v - start + newstart else v,\n  local remapChar(c, start, end, newstart) =\n    std.char(remap(\n      std.codepoint(c), std.codepoint(start), std.codepoint(end), std.codepoint(newstart)\n    )),\n  toLower(s):: (\n    std.join(\"\", [remapChar(c, \"A\", \"Z\", \"a\") for c in std.stringChars(s)])\n  ),\n  toUpper(s):: (\n    std.join(\"\", [remapChar(c, \"a\", \"z\", \"A\") for c in std.stringChars(s)])\n  ),\n\n  _Object(apiVersion, kind, name):: {\n    local this = self,\n    apiVersion: apiVersion,\n    kind: kind,\n    metadata: {\n      name: name,\n      labels: { name: std.join(\"-\", std.split(this.metadata.name, \":\")) },\n      annotations: {},\n    },\n  },\n\n  List(): {\n    apiVersion: \"v1\",\n    kind: \"List\",\n    items_:: {},\n    items: $.objectValues(self.items_),\n  },\n\n  Namespace(name): $._Object(\"v1\", \"Namespace\", name) {\n  },\n\n  Endpoints(name): $._Object(\"v1\", \"Endpoints\", name) {\n    Ip(addr):: { ip: addr },\n    Port(p):: { port: p },\n\n    subsets: [],\n  },\n\n  Service(name): $._Object(\"v1\", \"Service\", name) {\n    local service = self,\n\n    target_pod:: error \"service target_pod required\",\n    port:: self.target_pod.spec.containers[0].ports[0].containerPort,\n\n    // Helpers that format host:port in various ways\n    host:: \"%s.%s.svc\" % [self.metadata.name, self.metadata.namespace],\n    host_colon_port:: \"%s:%s\" % [self.host, self.spec.ports[0].port],\n    http_url:: \"http://%s/\" % self.host_colon_port,\n    proxy_urlpath:: \"/api/v1/proxy/namespaces/%s/services/%s/\" % [\n      self.metadata.namespace,\n      self.metadata.name,\n    ],\n    // Useful in Ingress rules\n    name_port:: {\n      serviceName: service.metadata.name,\n      servicePort: service.spec.ports[0].port,\n    },\n\n    spec: {\n      selector: service.target_pod.metadata.labels,\n      ports: [\n        {\n          port: service.port,\n          targetPort: service.target_pod.spec.containers[0].ports[0].containerPort,\n        },\n      ],\n      type: \"ClusterIP\",\n    },\n  },\n\n  PersistentVolume(name): $._Object(\"v1\", \"PersistentVolume\", name) {\n    spec: {},\n  },\n\n  // TODO: This is a terrible name\n  PersistentVolumeClaimVolume(pvc): {\n    persistentVolumeClaim: { claimName: pvc.metadata.name },\n  },\n\n  StorageClass(name): $._Object(\"storage.k8s.io/v1beta1\", \"StorageClass\", name) {\n    provisioner: error \"provisioner required\",\n  },\n\n  PersistentVolumeClaim(name): $._Object(\"v1\", \"PersistentVolumeClaim\", name) {\n    local pvc = self,\n\n    storageClass:: null,\n    storage:: error \"storage required\",\n\n    metadata+: if pvc.storageClass != null then {\n      annotations+: {\n        \"volume.beta.kubernetes.io/storage-class\": pvc.storageClass,\n      },\n    } else {},\n\n    spec: {\n      resources: {\n        requests: {\n          storage: pvc.storage,\n        },\n      },\n      accessModes: [\"ReadWriteOnce\"],\n      [if pvc.storageClass != null then \"storageClassName\"]: pvc.storageClass,\n    },\n  },\n\n  Container(name): {\n    name: name,\n    image: error \"container image value required\",\n    imagePullPolicy: if std.endsWith(self.image, \":latest\") then \"Always\" else \"IfNotPresent\",\n\n    envList(map):: [\n      if std.type(map[x]) == \"object\" then { name: x, valueFrom: map[x] } else { name: x, value: map[x] }\n      for x in std.objectFields(map)\n    ],\n\n    env_:: {},\n    env: self.envList(self.env_),\n\n    args_:: {},\n    args: [\"--%s=%s\" % kv for kv in $.objectItems(self.args_)],\n\n    ports_:: {},\n    ports: $.mapToNamedList(self.ports_),\n\n    volumeMounts_:: {},\n    volumeMounts: $.mapToNamedList(self.volumeMounts_),\n\n    stdin: false,\n    tty: false,\n    assert !self.tty || self.stdin : \"tty=true requires stdin=true\",\n  },\n\n  PodDisruptionBudget(name): $._Object(\"policy/v1beta1\", \"PodDisruptionBudget\", name) {\n    local this = self,\n    target_pod:: error \"target_pod required\",\n    spec: {\n      minAvailable: 1,\n      selector: {\n        matchLabels: this.target_pod.metadata.labels,\n      },\n    },\n  },\n\n  Pod(name): $._Object(\"v1\", \"Pod\", name) {\n    spec: $.PodSpec,\n  },\n\n  PodSpec: {\n    // The 'first' container is used in various defaults in k8s.\n    local container_names = std.objectFields(self.containers_),\n    default_container:: if std.length(container_names) > 1 then \"default\" else container_names[0],\n    containers_:: {},\n\n    local container_names_ordered = [self.default_container] + [n for n in container_names if n != self.default_container],\n    containers: [{ name: $.hyphenate(name) } + self.containers_[name] for name in container_names_ordered if self.containers_[name] != null],\n\n    // Note initContainers are inherently ordered, and using this\n    // named object will lose that ordering.  If order matters, then\n    // manipulate `initContainers` directly (perhaps\n    // appending/prepending to `super.initContainers` to mix+match\n    // both approaches)\n    initContainers_:: {},\n    initContainers: [{ name: $.hyphenate(name) } + self.initContainers_[name] for name in std.objectFields(self.initContainers_) if self.initContainers_[name] != null],\n\n    volumes_:: {},\n    volumes: $.mapToNamedList(self.volumes_),\n\n    imagePullSecrets: [],\n\n    terminationGracePeriodSeconds: 30,\n\n    assert std.length(self.containers) > 0 : \"must have at least one container\",\n\n    // Return an array of pod's ports numbers\n    ports(proto):: [\n      p.containerPort\n      for p in std.flattenArrays([\n        c.ports\n        for c in self.containers\n      ])\n      if (\n        (!(std.objectHas(p, \"protocol\")) && proto == \"TCP\")\n        ||\n        ((std.objectHas(p, \"protocol\")) && p.protocol == proto)\n      )\n    ],\n\n  },\n\n  EmptyDirVolume(): {\n    emptyDir: {},\n  },\n\n  HostPathVolume(path, type=\"\"): {\n    hostPath: { path: path, type: type },\n  },\n\n  GitRepoVolume(repository, revision): {\n    gitRepo: {\n      repository: repository,\n\n      // \"master\" is possible, but should be avoided for production\n      revision: revision,\n    },\n  },\n\n  SecretVolume(secret): {\n    secret: { secretName: secret.metadata.name },\n  },\n\n  ConfigMapVolume(configmap): {\n    configMap: { name: configmap.metadata.name },\n  },\n\n  ConfigMap(name): $._Object(\"v1\", \"ConfigMap\", name) {\n    data: {},\n\n    // I keep thinking data values can be any JSON type.  This check\n    // will remind me that they must be strings :(\n    local nonstrings = [\n      k\n      for k in std.objectFields(self.data)\n      if std.type(self.data[k]) != \"string\"\n    ],\n    assert std.length(nonstrings) == 0 : \"data contains non-string values: %s\" % [nonstrings],\n  },\n\n  // subtype of EnvVarSource\n  ConfigMapRef(configmap, key): {\n    assert std.objectHas(configmap.data, key) : \"%s not in configmap.data\" % [key],\n    configMapKeyRef: {\n      name: configmap.metadata.name,\n      key: key,\n    },\n  },\n\n  Secret(name): $._Object(\"v1\", \"Secret\", name) {\n    local secret = self,\n\n    type: \"Opaque\",\n    data_:: {},\n    data: { [k]: std.base64(secret.data_[k]) for k in std.objectFields(secret.data_) },\n  },\n\n  // subtype of EnvVarSource\n  SecretKeyRef(secret, key): {\n    assert std.objectHas(secret.data, key) : \"%s not in secret.data\" % [key],\n    secretKeyRef: {\n      name: secret.metadata.name,\n      key: key,\n    },\n  },\n\n  // subtype of EnvVarSource\n  FieldRef(key): {\n    fieldRef: {\n      apiVersion: \"v1\",\n      fieldPath: key,\n    },\n  },\n\n  // subtype of EnvVarSource\n  ResourceFieldRef(key, divisor=\"1\"): {\n    resourceFieldRef: {\n      resource: key,\n      divisor: std.toString(divisor),\n    },\n  },\n\n  Deployment(name): $._Object(\"apps/v1\", \"Deployment\", name) {\n    local deployment = self,\n\n    spec: {\n      template: {\n        spec: $.PodSpec,\n        metadata: {\n          labels: deployment.metadata.labels,\n          annotations: {},\n        },\n      },\n\n      selector: {\n        matchLabels: deployment.spec.template.metadata.labels,\n      },\n\n      strategy: {\n        type: \"RollingUpdate\",\n\n        local pvcs = [\n          v\n          for v in deployment.spec.template.spec.volumes\n          if std.objectHas(v, \"persistentVolumeClaim\")\n        ],\n        local is_stateless = std.length(pvcs) == 0,\n\n        // Apps trying to maintain a majority quorum or similar will\n        // want to tune these carefully.\n        // NB: Upstream default is surge=1 unavail=1\n        rollingUpdate: if is_stateless then {\n          maxSurge: \"25%\",  // rounds up\n          maxUnavailable: \"25%\",  // rounds down\n        } else {\n          // Poor-man's StatelessSet.  Useful mostly with replicas=1.\n          maxSurge: 0,\n          maxUnavailable: 1,\n        },\n      },\n\n      // NB: Upstream default is 0\n      minReadySeconds: 30,\n\n      // NB: Regular k8s default is to keep all revisions\n      revisionHistoryLimit: 10,\n\n      replicas: 1,\n    },\n  },\n\n  CrossVersionObjectReference(target): {\n    apiVersion: target.apiVersion,\n    kind: target.kind,\n    name: target.metadata.name,\n  },\n\n  HorizontalPodAutoscaler(name): $._Object(\"autoscaling/v1\", \"HorizontalPodAutoscaler\", name) {\n    local hpa = self,\n\n    target:: error \"target required\",\n\n    spec: {\n      scaleTargetRef: $.CrossVersionObjectReference(hpa.target),\n\n      minReplicas: hpa.target.spec.replicas,\n      maxReplicas: error \"maxReplicas required\",\n\n      assert self.maxReplicas >= self.minReplicas,\n    },\n  },\n\n  StatefulSet(name): $._Object(\"apps/v1\", \"StatefulSet\", name) {\n    local sset = self,\n\n    spec: {\n      serviceName: name,\n\n      updateStrategy: {\n        type: \"RollingUpdate\",\n        rollingUpdate: {\n          partition: 0,\n        },\n      },\n\n      template: {\n        spec: $.PodSpec,\n        metadata: {\n          labels: sset.metadata.labels,\n          annotations: {},\n        },\n      },\n\n      selector: {\n        matchLabels: sset.spec.template.metadata.labels,\n      },\n\n      volumeClaimTemplates_:: {},\n      volumeClaimTemplates: [\n        // StatefulSet is overly fussy about \"changes\" (even when\n        // they're no-ops).\n        // In particular annotations={} is apparently a \"change\",\n        // since the comparison is ignorant of defaults.\n        std.prune($.PersistentVolumeClaim($.hyphenate(kv[0])) + { apiVersion:: null, kind:: null } + kv[1])\n        for kv in $.objectItems(self.volumeClaimTemplates_)\n      ],\n\n      replicas: 1,\n      assert self.replicas >= 1,\n    },\n  },\n\n  Job(name): $._Object(\"batch/v1\", \"Job\", name) {\n    local job = self,\n\n    spec: $.JobSpec {\n      template+: {\n        metadata+: {\n          labels: job.metadata.labels,\n        },\n      },\n    },\n  },\n\n  // NB: kubernetes >= 1.8.x has batch/v1beta1 (olders were batch/v2alpha1)\n  CronJob(name): $._Object(\"batch/v1beta1\", \"CronJob\", name) {\n    local cronjob = self,\n\n    spec: {\n      jobTemplate: {\n        spec: $.JobSpec {\n          template+: {\n            metadata+: {\n              labels: cronjob.metadata.labels,\n            },\n          },\n        },\n      },\n\n      schedule: error \"Need to provide spec.schedule\",\n      successfulJobsHistoryLimit: 10,\n      failedJobsHistoryLimit: 20,\n      // NB: upstream concurrencyPolicy default is \"Allow\"\n      concurrencyPolicy: \"Forbid\",\n    },\n  },\n\n  JobSpec: {\n    local this = self,\n\n    template: {\n      spec: $.PodSpec {\n        restartPolicy: \"OnFailure\",\n      },\n    },\n\n    completions: 1,\n    parallelism: 1,\n  },\n\n  DaemonSet(name): $._Object(\"apps/v1\", \"DaemonSet\", name) {\n    local ds = self,\n    spec: {\n      updateStrategy: {\n        type: \"RollingUpdate\",\n        rollingUpdate: {\n          maxUnavailable: 1,\n        },\n      },\n      template: {\n        metadata: {\n          labels: ds.metadata.labels,\n          annotations: {},\n        },\n        spec: $.PodSpec,\n      },\n\n      selector: {\n        matchLabels: ds.spec.template.metadata.labels,\n      },\n    },\n  },\n\n  Ingress(name): $._Object(\"extensions/v1beta1\", \"Ingress\", name) {\n    spec: {},\n\n    local rel_paths = [\n      p.path\n      for r in self.spec.rules\n      for p in r.http.paths\n      if !std.startsWith(p.path, \"/\")\n    ],\n    assert std.length(rel_paths) == 0 : \"paths must be absolute: \" + rel_paths,\n  },\n\n  ThirdPartyResource(name): $._Object(\"extensions/v1beta1\", \"ThirdPartyResource\", name) {\n    versions_:: [],\n    versions: [{ name: n } for n in self.versions_],\n  },\n\n  CustomResourceDefinition(group, version, kind): {\n    local this = self,\n    apiVersion: \"apiextensions.k8s.io/v1beta1\",\n    kind: \"CustomResourceDefinition\",\n    metadata+: {\n      name: this.spec.names.plural + \".\" + this.spec.group,\n    },\n    spec: {\n      scope: \"Namespaced\",\n      group: group,\n      version: version,\n      names: {\n        kind: kind,\n        singular: $.toLower(self.kind),\n        plural: self.singular + \"s\",\n        listKind: self.kind + \"List\",\n      },\n    },\n  },\n\n  ServiceAccount(name): $._Object(\"v1\", \"ServiceAccount\", name) {\n  },\n\n  Role(name): $._Object(\"rbac.authorization.k8s.io/v1\", \"Role\", name) {\n    rules: [],\n  },\n\n  ClusterRole(name): $.Role(name) {\n    kind: \"ClusterRole\",\n  },\n\n  Group(name): {\n    kind: \"Group\",\n    name: name,\n    apiGroup: \"rbac.authorization.k8s.io\",\n  },\n\n  User(name): {\n    kind: \"User\",\n    name: name,\n    apiGroup: \"rbac.authorization.k8s.io\",\n  },\n\n  RoleBinding(name): $._Object(\"rbac.authorization.k8s.io/v1\", \"RoleBinding\", name) {\n    local rb = self,\n\n    subjects_:: [],\n    subjects: [{\n      kind: o.kind,\n      namespace: o.metadata.namespace,\n      name: o.metadata.name,\n    } for o in self.subjects_],\n\n    roleRef_:: error \"roleRef is required\",\n    roleRef: {\n      apiGroup: \"rbac.authorization.k8s.io\",\n      kind: rb.roleRef_.kind,\n      name: rb.roleRef_.metadata.name,\n    },\n  },\n\n  ClusterRoleBinding(name): $.RoleBinding(name) {\n    kind: \"ClusterRoleBinding\",\n  },\n\n  // NB: datalines_ can be used to reduce boilerplate importstr as:\n  // kubectl get secret ... -ojson mysec | kubeseal | jq -r .spec.data > mysec-ssdata.txt\n  //   datalines_: importstr \"mysec-ssddata.txt\"\n  SealedSecret(name): $._Object(\"bitnami.com/v1alpha1\", \"SealedSecret\", name) {\n    spec: {\n      data:\n        if self.datalines_ != \"\"\n        then std.join(\"\", std.split(self.datalines_, \"\\n\"))\n        else error \"data or datalines_ required (output from: kubeseal | jq -r .spec.data)\",\n      datalines_:: \"\",\n    },\n    assert std.base64Decode(self.spec.data) != \"\",\n  },\n\n  // NB: helper method to access several Kubernetes objects podRef,\n  // used below to extract its labels\n  podRef(obj):: ({\n                   Pod: obj,\n                   Deployment: obj.spec.template,\n                   StatefulSet: obj.spec.template,\n                   DaemonSet: obj.spec.template,\n                   Job: obj.spec.template,\n                   CronJob: obj.spec.jobTemplate.spec.template,\n                 }[obj.kind]),\n\n  // NB: return a { podSelector: ... } ready to use for e.g. NSPs (see below)\n  // pod labels can be optionally filtered by their label name 2nd array arg\n  podLabelsSelector(obj, filter=null):: {\n    podSelector: std.prune({\n      matchLabels:\n        if filter != null then $.filterMapByFields($.podRef(obj).metadata.labels, filter)\n        else $.podRef(obj).metadata.labels,\n    }),\n  },\n\n  // NB: Returns an array as [{ port: num, protocol: \"PROTO\" }, {...}, ... ]\n  // Need to split TCP, UDP logic to be able to dedup each set of protocol ports\n  podsPorts(obj_list):: std.flattenArrays([\n    [\n      { port: port, protocol: protocol }\n      for port in std.set(\n        std.flattenArrays([$.podRef(obj).spec.ports(protocol) for obj in obj_list])\n      )\n    ]\n    for protocol in [\"TCP\", \"UDP\"]\n  ]),\n\n  // NB: most of the \"helper\" stuff comes from above (podLabelsSelector, podsPorts),\n  // NetworkPolicy returned object will have \"Ingress\", \"Egress\" policyTypes auto-set\n  // based on populated spec.ingress or spec.egress\n  // See tests/test-simple-validate.jsonnet for example(s).\n  NetworkPolicy(name): $._Object(\"networking.k8s.io/v1\", \"NetworkPolicy\", name) {\n    local networkpolicy = self,\n    spec: {\n      policyTypes: std.prune([\n        if networkpolicy.spec.ingress != [] then \"Ingress\" else null,\n        if networkpolicy.spec.egress != [] then \"Egress\" else null,\n      ]),\n      ingress: $.objectValues(self.ingress_),\n      ingress_:: {},\n      egress: $.objectValues(self.egress_),\n      egress_:: {},\n      podSelector: {},\n    },\n  },\n}\n"
  },
  {
    "path": "vendor_jsonnet/kube-libsonnet/tests/Dockerfile",
    "content": "FROM bitnami/minideb:buster\nLABEL org.opencontainers.image.authors=\"sre@bitnami.com\"\n\nARG jsonnet_version=0.14.0\nARG kubectl_version=v1.13.0\nARG kubecfg_version=v0.12.0\n\nRUN install_packages jq make curl ca-certificates\nRUN adduser --home /home/user --disabled-password --gecos User user\n\nRUN curl -sLo /tmp/jsonnet-v${jsonnet_version}.tar.gz https://github.com/google/jsonnet/releases/download/v${jsonnet_version}/jsonnet-bin-v${jsonnet_version}-linux.tar.gz\nRUN tar -zxf /tmp/jsonnet-v${jsonnet_version}.tar.gz -C /tmp && mv /tmp/jsonnet /tmp/jsonnetfmt /usr/local/bin\n\nRUN curl -sLo /usr/local/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/${kubectl_version}/bin/linux/amd64/kubectl\nRUN chmod +x /usr/local/bin/kubectl\n\nRUN curl -sLo /usr/local/bin/kubecfg https://github.com/bitnami/kubecfg/releases/download/${kubecfg_version}/kubecfg-linux-amd64\nRUN chmod +x /usr/local/bin/kubecfg\n\nUSER user\nWORKDIR /home/user\nCMD [\"/bin/bash\", \"-l\"]\n"
  },
  {
    "path": "vendor_jsonnet/kube-libsonnet/tests/Makefile",
    "content": "SHELL=/bin/bash\nJSONNET_FMT=--indent 2 --string-style d --comment-style s --no-pad-arrays --pad-objects --pretty-field-names\n\nALL_JSONNET=$(wildcard *.jsonnet)\nUNITTEST_JSONNET=$(wildcard unittest*.jsonnet)\nLIB_JSONNET=$(wildcard ../*.libsonnet)\nALL_K8S_VALIDATE_JSONNET=$(wildcard *-validate.jsonnet)\n\nPHONY_GOLDEN=$(patsubst %.jsonnet,golden/%.json,$(ALL_JSONNET))\nPHONY_DIFF=$(patsubst %.jsonnet,%.diff,$(ALL_JSONNET))\nPHONY_PARSE=$(patsubst %.jsonnet,%.parse,$(ALL_JSONNET))\n\n## These need to be in-sync with docker-compose.yaml\nDOCKER_E2E=e2e-test\nTMP_RANCHER=./tmp-rancher\nPROJECT=kubelibsonnet\n\ntests: docker-compose-tests\n\ndocker-compose-tests:\n\tinstall -d $(TMP_RANCHER) $(TMP_RANCHER)/etc && touch $(TMP_RANCHER)/etc/k3s.yaml\n\tUSERID=$$(id -u) docker-compose -p $(PROJECT) up -d\n\trc=$$(timeout 60s docker wait $(DOCKER_E2E)) || rc=255 ;\\\n\t   test $$rc -ne 0 && docker logs k3s-api;\\\n\t   docker logs $(DOCKER_E2E);\\\n\t   docker-compose -p $(PROJECT) down;\\\n\t   exit $$rc\n\trm -rf ./$(TMP_RANCHER)\n\ntest-srcs: unittests lint parse diff\ntest-kube: validate\n\n# NB: unittest jsonnet files are also covered by parse and diff targets,\n#     called out here for convenience\nunittests:\n\tjsonnet $(UNITTEST_JSONNET)\n\nlint:\n\t@set -e; errs=0; \\\n        for f in $(ALL_JSONNET) $(LIB_JSONNET); do \\\n\t  if ! jsonnetfmt --test $(JSONNET_FMT) -- $$f; then \\\n\t    echo \"FAILED lint: $$f\" >&2; \\\n\t    errs=$$(( $$errs + 1 )); \\\n\t  fi; \\\n\tdone; \\\n\tif [ $$errs -gt 0 ]; then \\\n\t  echo \"NOTE: if the 'lint' target fails, run:\"; \\\n\t  echo \"      $(MAKE) fix-lint lint\"; \\\n\t  exit 1; \\\n\tfi\n\nparse: $(PHONY_PARSE)\n\ndiff: diff-help $(PHONY_DIFF)\n\nvalidate:\n\ttimeout 10 kubectl api-versions > /dev/null \\\n\t|| { echo \"WARNING: no usable runtime kube context, skipping.\"; exit 0 ;} \\\n\t&& kubectl version --short && kubecfg version && kubecfg validate --ignore-unknown=false $(ALL_K8S_VALIDATE_JSONNET)\n\n%.diff: %.jsonnet\n\tdiff -u golden/$(*).json <(jsonnet $(<))\n\n%.parse: %.jsonnet\n\tjsonnet $(<) > /dev/null\n\ngolden/%.json: %.jsonnet\n\tjsonnet $(<) > $(@)\n\ndiff-help:\n\t@echo \"NOTE: if the 'diff' target fails, review output and run:\"\n\t@echo \"      $(MAKE) gen-golden diff\"\n\t@echo\n\nfix-lint:\n\t@set -e; \\\n\tfor f in $(ALL_JSONNET) $(LIB_JSONNET); do \\\n\t  echo jsonnetfmt -i $(JSONNET_FMT) -- $$f; \\\n\t  jsonnetfmt -i $(JSONNET_FMT) -- $$f; \\\n\tdone\n\ngen-golden: $(PHONY_GOLDEN)\n\n.PHONY: unittests lint parse validate diff %.parse %.diff golden/%.json diff-help fix-lint gen-golden\n"
  },
  {
    "path": "vendor_jsonnet/kube-libsonnet/tests/docker-compose.yaml",
    "content": "version: \"3\"\nservices:\n  kube-api:\n    image: rancher/k3s:${K3S_VERSION}\n    command: server --disable-agent\n    container_name: k3s-api\n    volumes:\n      - ./tmp-rancher:/.kube\n      - ./tmp-rancher:/.rancher\n      - ./tmp-rancher/etc:/etc/rancher/k3s\n    expose:\n      - 6443\n    user: \"${USERID}\"\n    environment:\n      - USER=nobody\n      - HOME=/\n  e2e-test:\n    build: .\n    container_name: e2e-test\n    links:\n      - \"kube-api:kube-api\"\n    depends_on:\n      - kube-api\n    volumes:\n      - ./tmp-rancher:/tmp/rancher\n      - ..:/work\n    working_dir: /work\n    environment:\n      - HOME=/\n    user: \"${USERID}\"\n    command:\n      - bash\n      - -c\n      - |\n        echo \"INFO: Starting tests: unit, lint ...\"\n        make -C tests test-srcs\n        export KUBECONFIG=/tmp/kubeconfig\n        echo \"INFO: Waiting for kube-api to be available ...\"\n        until kubectl get nodes; do\n          sleep 1\n          # Found that k3s releases create k3s.yaml under diff paths,\n          # redirecting stderr just to avoid red-herrings errors\n          sed -e s/localhost/kube-api/ -e s/127.0.0.1/kube-api/ \\\n            /tmp/rancher/k3s.yaml /tmp/rancher/etc/k3s.yaml \\\n            > $$KUBECONFIG 2>/dev/null\n        done\n        echo \"INFO: Starting tests: validate ...\"\n        set -x\n        make -C tests test-kube\n"
  },
  {
    "path": "vendor_jsonnet/kube-libsonnet/tests/golden/test-sealedsecrets-datalines.json",
    "content": "{\n   \"apiVersion\": \"v1\",\n   \"items\": [\n      {\n         \"apiVersion\": \"bitnami.com/v1alpha1\",\n         \"kind\": \"SealedSecret\",\n         \"metadata\": {\n            \"annotations\": { },\n            \"labels\": {\n               \"name\": \"foo\"\n            },\n            \"name\": \"foo\"\n         },\n         \"spec\": {\n            \"data\": \"AgCz+HLrnNCZoDCOcCbMgKp//+Tfz5ecNcVGZOnLT/M/RP5E7u6ifHtm3GfG8qJKcRK9v6ZBYOIeDqenlSNMpRmigXH0p2ThMfmBcPIYfLvqOcwfwssfOS4jp02qsAjSfaAXuL2Mze15OnXImiPHsoi9BIrJnyhRObBeRMnJ4xMUAFPih1CfqY3nl7FQhH6+4t3Wh52JY3VmDWYYGH5Kc5JyS40PodCUEgId1kJKtChUPQ0JsTgj6W8ymyJ0bC8jyzrZHD0gLo26T+7mNiJyUEU9qj7p8YhZj3v6VcEv4DKQVVvkGk94svTAdnSQrh5cNOaZrp6eTJqIs+PcrelTVitoZzZsnIc+ze6aPjGhft752xa9CDmuCkQ9XkWfWpGcGrEv1jmIPkJYIjPIKgP6CS5eU+eBZNIkNpIof2jiYdqqP59BhoBwVkC/Rm1GfgOdLxO55sicTwSmRA+/pkE6hopK327uwxWmtGZ5/kUuW6nZ4shTNTsr1n1skZJvw6UfwkAgIYuDctHqw+y7eSdDM2gggLM72TgBEf5111LnIj2rroQu5bR6XVVpMy6QFhE9LLAC3kcs/BqPh5A7qq/ffZrFhWpSIS7voavtdqn6XhdJ8TsJ7Wbkhjxf1S/l/YqS6B1ZlJFlrdcNA6BHqzyjncmrD030YfwNqYUl2Itxok7S6DaImzBkxV5Uqd18EiuVcbVL3Ic1I3B4pviBAo0eG5nn4/uZCjuBKu6muLtR5GFMblbqV23MiB8Q3jtODvVw6SbFfNQx836RsJAFSmE+axSGvZPq4zDXGUYekOHZ+ov6Gd2Czv99Cf67r3ogJIzDR1uVRZH78bYrjEAkRGpKETUfLFfRyRZzWW/K5cKEAoDttzHI5grlgm44k5RQeaoargmyVjuEgjEZ2yZnl5Z7BX3YSazcLfK2t/wwGNHMMgedf3SeGETIx552CehRw7+A1+0q4iKQ9z13dPpjAYNzmLd1bGY4ORQeEf4Duk6NTXXHfpyRSq/CNK5BPhKnTn1OEnUQ+ttD/ZjNkx2OMFsX7zfMfk0RRRnKlWpanYOtTinx5WbpIYOKZ4Labb7+CnuHMgTZoNTXZe2DnNFqWIgm47U=\"\n         }\n      }\n   ],\n   \"kind\": \"List\"\n}\n"
  },
  {
    "path": "vendor_jsonnet/kube-libsonnet/tests/golden/test-sealedsecrets.json",
    "content": "{\n   \"apiVersion\": \"v1\",\n   \"items\": [\n      {\n         \"apiVersion\": \"bitnami.com/v1alpha1\",\n         \"kind\": \"SealedSecret\",\n         \"metadata\": {\n            \"annotations\": { },\n            \"labels\": {\n               \"name\": \"foo\"\n            },\n            \"name\": \"foo\"\n         },\n         \"spec\": {\n            \"data\": \"dGVzdAo=\"\n         }\n      }\n   ],\n   \"kind\": \"List\"\n}\n"
  },
  {
    "path": "vendor_jsonnet/kube-libsonnet/tests/golden/test-simple-validate.json",
    "content": "{\n   \"apiVersion\": \"v1\",\n   \"items\": [\n      {\n         \"apiVersion\": \"v1\",\n         \"data\": {\n            \"foo_key\": \"bar_val\"\n         },\n         \"kind\": \"ConfigMap\",\n         \"metadata\": {\n            \"annotations\": { },\n            \"labels\": {\n               \"name\": \"foo-config\"\n            },\n            \"name\": \"foo-config\",\n            \"namespace\": \"foons\"\n         }\n      },\n      {\n         \"apiVersion\": \"batch/v1beta1\",\n         \"kind\": \"CronJob\",\n         \"metadata\": {\n            \"annotations\": { },\n            \"labels\": {\n               \"name\": \"foo-cronjob\"\n            },\n            \"name\": \"foo-cronjob\",\n            \"namespace\": \"foons\"\n         },\n         \"spec\": {\n            \"concurrencyPolicy\": \"Forbid\",\n            \"failedJobsHistoryLimit\": 20,\n            \"jobTemplate\": {\n               \"spec\": {\n                  \"completions\": 1,\n                  \"parallelism\": 1,\n                  \"template\": {\n                     \"metadata\": {\n                        \"labels\": {\n                           \"name\": \"foo-cronjob\"\n                        }\n                     },\n                     \"spec\": {\n                        \"containers\": [\n                           {\n                              \"args\": [ ],\n                              \"env\": [ ],\n                              \"image\": \"busybox\",\n                              \"imagePullPolicy\": \"IfNotPresent\",\n                              \"name\": \"foo\",\n                              \"ports\": [ ],\n                              \"stdin\": false,\n                              \"tty\": false,\n                              \"volumeMounts\": [ ]\n                           }\n                        ],\n                        \"imagePullSecrets\": [ ],\n                        \"initContainers\": [ ],\n                        \"restartPolicy\": \"OnFailure\",\n                        \"terminationGracePeriodSeconds\": 30,\n                        \"volumes\": [ ]\n                     }\n                  }\n               }\n            },\n            \"schedule\": \"0 * * * *\",\n            \"successfulJobsHistoryLimit\": 10\n         }\n      },\n      {\n         \"apiVersion\": \"apps/v1\",\n         \"kind\": \"Deployment\",\n         \"metadata\": {\n            \"annotations\": { },\n            \"labels\": {\n               \"name\": \"foo-deploy\"\n            },\n            \"name\": \"foo-deploy\",\n            \"namespace\": \"foons\"\n         },\n         \"spec\": {\n            \"minReadySeconds\": 30,\n            \"replicas\": 1,\n            \"revisionHistoryLimit\": 10,\n            \"selector\": {\n               \"matchLabels\": {\n                  \"name\": \"foo-deploy\"\n               }\n            },\n            \"strategy\": {\n               \"rollingUpdate\": {\n                  \"maxSurge\": \"25%\",\n                  \"maxUnavailable\": \"25%\"\n               },\n               \"type\": \"RollingUpdate\"\n            },\n            \"template\": {\n               \"metadata\": {\n                  \"annotations\": { },\n                  \"labels\": {\n                     \"name\": \"foo-deploy\"\n                  }\n               },\n               \"spec\": {\n                  \"containers\": [\n                     {\n                        \"args\": [ ],\n                        \"env\": [\n                           {\n                              \"name\": \"my_secret\",\n                              \"valueFrom\": {\n                                 \"secretKeyRef\": {\n                                    \"key\": \"sec_key\",\n                                    \"name\": \"foo-secret\"\n                                 }\n                              }\n                           }\n                        ],\n                        \"image\": \"nginx:1.12\",\n                        \"imagePullPolicy\": \"IfNotPresent\",\n                        \"name\": \"foo\",\n                        \"ports\": [\n                           {\n                              \"containerPort\": 80,\n                              \"name\": \"http\"\n                           },\n                           {\n                              \"containerPort\": 888,\n                              \"name\": \"udp-port\",\n                              \"protocol\": \"UDP\"\n                           }\n                        ],\n                        \"stdin\": false,\n                        \"tty\": false,\n                        \"volumeMounts\": [\n                           {\n                              \"mountPath\": \"/config\",\n                              \"name\": \"config-vol\"\n                           }\n                        ]\n                     }\n                  ],\n                  \"imagePullSecrets\": [ ],\n                  \"initContainers\": [ ],\n                  \"serviceAccountName\": \"foo-sa\",\n                  \"terminationGracePeriodSeconds\": 30,\n                  \"volumes\": [\n                     {\n                        \"configMap\": {\n                           \"name\": \"foo-config\"\n                        },\n                        \"name\": \"config-vol\"\n                     }\n                  ]\n               }\n            }\n         }\n      },\n      {\n         \"apiVersion\": \"apps/v1\",\n         \"kind\": \"DaemonSet\",\n         \"metadata\": {\n            \"annotations\": { },\n            \"labels\": {\n               \"name\": \"foo-ds\"\n            },\n            \"name\": \"foo-ds\",\n            \"namespace\": \"foons\"\n         },\n         \"spec\": {\n            \"selector\": {\n               \"matchLabels\": {\n                  \"name\": \"foo-ds\"\n               }\n            },\n            \"template\": {\n               \"metadata\": {\n                  \"annotations\": { },\n                  \"labels\": {\n                     \"name\": \"foo-ds\"\n                  }\n               },\n               \"spec\": {\n                  \"containers\": [\n                     {\n                        \"args\": [ ],\n                        \"env\": [\n                           {\n                              \"name\": \"my_secret\",\n                              \"valueFrom\": {\n                                 \"secretKeyRef\": {\n                                    \"key\": \"sec_key\",\n                                    \"name\": \"foo-secret\"\n                                 }\n                              }\n                           }\n                        ],\n                        \"image\": \"nginx:1.12\",\n                        \"imagePullPolicy\": \"IfNotPresent\",\n                        \"name\": \"foo\",\n                        \"ports\": [\n                           {\n                              \"containerPort\": 80,\n                              \"name\": \"http\"\n                           },\n                           {\n                              \"containerPort\": 888,\n                              \"name\": \"udp-port\",\n                              \"protocol\": \"UDP\"\n                           }\n                        ],\n                        \"stdin\": false,\n                        \"tty\": false,\n                        \"volumeMounts\": [\n                           {\n                              \"mountPath\": \"/config\",\n                              \"name\": \"config-vol\"\n                           }\n                        ]\n                     }\n                  ],\n                  \"imagePullSecrets\": [ ],\n                  \"initContainers\": [ ],\n                  \"terminationGracePeriodSeconds\": 30,\n                  \"volumes\": [\n                     {\n                        \"configMap\": {\n                           \"name\": \"foo-config\"\n                        },\n                        \"name\": \"config-vol\"\n                     }\n                  ]\n               }\n            },\n            \"updateStrategy\": {\n               \"rollingUpdate\": {\n                  \"maxUnavailable\": 1\n               },\n               \"type\": \"RollingUpdate\"\n            }\n         }\n      },\n      {\n         \"apiVersion\": \"extensions/v1beta1\",\n         \"kind\": \"Ingress\",\n         \"metadata\": {\n            \"annotations\": {\n               \"certmanager.k8s.io/acme-challenge-type\": \"dns01\",\n               \"certmanager.k8s.io/acme-dns01-provider\": \"default\",\n               \"certmanager.k8s.io/cluster-issuer\": \"letsencrypt-prod-dns\"\n            },\n            \"labels\": {\n               \"name\": \"foo-ingress\"\n            },\n            \"name\": \"foo-ingress\",\n            \"namespace\": \"foons\"\n         },\n         \"spec\": {\n            \"rules\": [\n               {\n                  \"host\": \"foo.g.dev.bitnami.net\",\n                  \"http\": {\n                     \"paths\": [\n                        {\n                           \"backend\": {\n                              \"serviceName\": \"foo-svc\",\n                              \"servicePort\": 80\n                           },\n                           \"path\": \"/\"\n                        }\n                     ]\n                  }\n               }\n            ],\n            \"tls\": [\n               {\n                  \"hosts\": [\n                     \"foo.g.dev.bitnami.net\"\n                  ],\n                  \"secretName\": \"foo-ingress-cert\"\n               }\n            ]\n         }\n      },\n      {\n         \"apiVersion\": \"batch/v1\",\n         \"kind\": \"Job\",\n         \"metadata\": {\n            \"annotations\": { },\n            \"labels\": {\n               \"name\": \"foo-job\"\n            },\n            \"name\": \"foo-job\",\n            \"namespace\": \"foons\"\n         },\n         \"spec\": {\n            \"completions\": 1,\n            \"parallelism\": 1,\n            \"template\": {\n               \"metadata\": {\n                  \"labels\": {\n                     \"name\": \"foo-job\"\n                  }\n               },\n               \"spec\": {\n                  \"containers\": [\n                     {\n                        \"args\": [ ],\n                        \"env\": [ ],\n                        \"image\": \"busybox\",\n                        \"imagePullPolicy\": \"IfNotPresent\",\n                        \"name\": \"foo\",\n                        \"ports\": [ ],\n                        \"stdin\": false,\n                        \"tty\": false,\n                        \"volumeMounts\": [ ]\n                     }\n                  ],\n                  \"imagePullSecrets\": [ ],\n                  \"initContainers\": [ ],\n                  \"restartPolicy\": \"OnFailure\",\n                  \"terminationGracePeriodSeconds\": 30,\n                  \"volumes\": [ ]\n               }\n            }\n         }\n      },\n      {\n         \"apiVersion\": \"v1\",\n         \"kind\": \"Namespace\",\n         \"metadata\": {\n            \"annotations\": { },\n            \"labels\": {\n               \"name\": \"foons\"\n            },\n            \"name\": \"foons\"\n         }\n      },\n      {\n         \"apiVersion\": \"networking.k8s.io/v1\",\n         \"kind\": \"NetworkPolicy\",\n         \"metadata\": {\n            \"annotations\": { },\n            \"labels\": {\n               \"name\": \"foo-nsp-pods\"\n            },\n            \"name\": \"foo-nsp-pods\",\n            \"namespace\": \"foons\"\n         },\n         \"spec\": {\n            \"egress\": [\n               {\n                  \"ports\": [\n                     {\n                        \"port\": 53,\n                        \"protocol\": \"UDP\"\n                     }\n                  ],\n                  \"to\": [\n                     {\n                        \"namespaceSelector\": {\n                           \"matchLabels\": {\n                              \"name\": \"kube-system\"\n                           }\n                        }\n                     }\n                  ]\n               },\n               {\n                  \"ports\": [\n                     {\n                        \"port\": 80,\n                        \"protocol\": \"TCP\"\n                     },\n                     {\n                        \"port\": 888,\n                        \"protocol\": \"UDP\"\n                     }\n                  ],\n                  \"to\": [\n                     {\n                        \"podSelector\": {\n                           \"matchLabels\": {\n                              \"name\": \"foo-sts\"\n                           }\n                        }\n                     }\n                  ]\n               }\n            ],\n            \"ingress\": [\n               {\n                  \"from\": [\n                     {\n                        \"podSelector\": {\n                           \"matchLabels\": {\n                              \"name\": \"foo-job\"\n                           }\n                        }\n                     },\n                     {\n                        \"podSelector\": {\n                           \"matchLabels\": {\n                              \"name\": \"foo-cronjob\"\n                           }\n                        }\n                     },\n                     {\n                        \"namespaceSelector\": {\n                           \"matchLabels\": {\n                              \"name\": \"nginx-ingress\"\n                           }\n                        }\n                     }\n                  ],\n                  \"ports\": [\n                     {\n                        \"port\": 80,\n                        \"protocol\": \"TCP\"\n                     },\n                     {\n                        \"port\": 888,\n                        \"protocol\": \"UDP\"\n                     }\n                  ]\n               }\n            ],\n            \"podSelector\": {\n               \"matchLabels\": {\n                  \"name\": \"foo-deploy\"\n               }\n            },\n            \"policyTypes\": [\n               \"Ingress\",\n               \"Egress\"\n            ]\n         }\n      },\n      {\n         \"apiVersion\": \"v1\",\n         \"kind\": \"Pod\",\n         \"metadata\": {\n            \"annotations\": { },\n            \"labels\": {\n               \"name\": \"foo-pod\"\n            },\n            \"name\": \"foo-pod\",\n            \"namespace\": \"foons\"\n         },\n         \"spec\": {\n            \"containers\": [\n               {\n                  \"args\": [ ],\n                  \"env\": [\n                     {\n                        \"name\": \"my_secret\",\n                        \"valueFrom\": {\n                           \"secretKeyRef\": {\n                              \"key\": \"sec_key\",\n                              \"name\": \"foo-secret\"\n                           }\n                        }\n                     }\n                  ],\n                  \"image\": \"nginx:1.12\",\n                  \"imagePullPolicy\": \"IfNotPresent\",\n                  \"name\": \"foo\",\n                  \"ports\": [\n                     {\n                        \"containerPort\": 80,\n                        \"name\": \"http\"\n                     },\n                     {\n                        \"containerPort\": 888,\n                        \"name\": \"udp-port\",\n                        \"protocol\": \"UDP\"\n                     }\n                  ],\n                  \"stdin\": false,\n                  \"tty\": false,\n                  \"volumeMounts\": [\n                     {\n                        \"mountPath\": \"/config\",\n                        \"name\": \"config-vol\"\n                     }\n                  ]\n               }\n            ],\n            \"imagePullSecrets\": [ ],\n            \"initContainers\": [ ],\n            \"terminationGracePeriodSeconds\": 30,\n            \"volumes\": [\n               {\n                  \"configMap\": {\n                     \"name\": \"foo-config\"\n                  },\n                  \"name\": \"config-vol\"\n               }\n            ]\n         }\n      },\n      {\n         \"apiVersion\": \"rbac.authorization.k8s.io/v1\",\n         \"kind\": \"Role\",\n         \"metadata\": {\n            \"annotations\": { },\n            \"labels\": {\n               \"name\": \"foo-role\"\n            },\n            \"name\": \"foo-role\",\n            \"namespace\": \"foons\"\n         },\n         \"rules\": [\n            {\n               \"apiGroups\": [\n                  \"\"\n               ],\n               \"resources\": [\n                  \"pods\",\n                  \"secrets\",\n                  \"configmaps\",\n                  \"persistentvolumeclaims\"\n               ],\n               \"verbs\": [\n                  \"get\"\n               ]\n            },\n            {\n               \"apiGroups\": [\n                  \"\"\n               ],\n               \"resources\": [\n                  \"pods\"\n               ],\n               \"verbs\": [\n                  \"patch\"\n               ]\n            }\n         ]\n      },\n      {\n         \"apiVersion\": \"rbac.authorization.k8s.io/v1\",\n         \"kind\": \"RoleBinding\",\n         \"metadata\": {\n            \"annotations\": { },\n            \"labels\": {\n               \"name\": \"foo-rolebinding\"\n            },\n            \"name\": \"foo-rolebinding\",\n            \"namespace\": \"foons\"\n         },\n         \"roleRef\": {\n            \"apiGroup\": \"rbac.authorization.k8s.io\",\n            \"kind\": \"Role\",\n            \"name\": \"foo-role\"\n         },\n         \"subjects\": [\n            {\n               \"kind\": \"ServiceAccount\",\n               \"name\": \"foo-sa\",\n               \"namespace\": \"foons\"\n            }\n         ]\n      },\n      {\n         \"apiVersion\": \"v1\",\n         \"kind\": \"ServiceAccount\",\n         \"metadata\": {\n            \"annotations\": { },\n            \"labels\": {\n               \"name\": \"foo-sa\"\n            },\n            \"name\": \"foo-sa\",\n            \"namespace\": \"foons\"\n         }\n      },\n      {\n         \"apiVersion\": \"v1\",\n         \"data\": {\n            \"sec_key\": \"c2VjcmV0Cg==\"\n         },\n         \"kind\": \"Secret\",\n         \"metadata\": {\n            \"annotations\": { },\n            \"labels\": {\n               \"name\": \"foo-secret\"\n            },\n            \"name\": \"foo-secret\",\n            \"namespace\": \"foons\"\n         },\n         \"type\": \"Opaque\"\n      },\n      {\n         \"apiVersion\": \"v1\",\n         \"kind\": \"Service\",\n         \"metadata\": {\n            \"annotations\": { },\n            \"labels\": {\n               \"name\": \"foo-svc\"\n            },\n            \"name\": \"foo-svc\",\n            \"namespace\": \"foons\"\n         },\n         \"spec\": {\n            \"ports\": [\n               {\n                  \"port\": 80,\n                  \"targetPort\": 80\n               }\n            ],\n            \"selector\": {\n               \"name\": \"foo-deploy\"\n            },\n            \"type\": \"ClusterIP\"\n         }\n      },\n      {\n         \"apiVersion\": \"apps/v1\",\n         \"kind\": \"StatefulSet\",\n         \"metadata\": {\n            \"annotations\": { },\n            \"labels\": {\n               \"name\": \"foo-sts\"\n            },\n            \"name\": \"foo-sts\",\n            \"namespace\": \"foons\"\n         },\n         \"spec\": {\n            \"replicas\": 1,\n            \"selector\": {\n               \"matchLabels\": {\n                  \"name\": \"foo-sts\"\n               }\n            },\n            \"serviceName\": \"foo-sts\",\n            \"template\": {\n               \"metadata\": {\n                  \"annotations\": { },\n                  \"labels\": {\n                     \"name\": \"foo-sts\"\n                  }\n               },\n               \"spec\": {\n                  \"containers\": [\n                     {\n                        \"args\": [ ],\n                        \"env\": [\n                           {\n                              \"name\": \"my_secret\",\n                              \"valueFrom\": {\n                                 \"secretKeyRef\": {\n                                    \"key\": \"sec_key\",\n                                    \"name\": \"foo-secret\"\n                                 }\n                              }\n                           }\n                        ],\n                        \"image\": \"nginx:1.12\",\n                        \"imagePullPolicy\": \"IfNotPresent\",\n                        \"name\": \"foo\",\n                        \"ports\": [\n                           {\n                              \"containerPort\": 80,\n                              \"name\": \"http\"\n                           },\n                           {\n                              \"containerPort\": 888,\n                              \"name\": \"udp-port\",\n                              \"protocol\": \"UDP\"\n                           }\n                        ],\n                        \"stdin\": false,\n                        \"tty\": false,\n                        \"volumeMounts\": [\n                           {\n                              \"mountPath\": \"/config\",\n                              \"name\": \"config-vol\"\n                           },\n                           {\n                              \"mountPath\": \"/foo/data\",\n                              \"name\": \"datadir\"\n                           }\n                        ]\n                     }\n                  ],\n                  \"imagePullSecrets\": [ ],\n                  \"initContainers\": [ ],\n                  \"serviceAccountName\": \"foo-sa\",\n                  \"terminationGracePeriodSeconds\": 30,\n                  \"volumes\": [\n                     {\n                        \"configMap\": {\n                           \"name\": \"foo-config\"\n                        },\n                        \"name\": \"config-vol\"\n                     }\n                  ]\n               }\n            },\n            \"updateStrategy\": {\n               \"rollingUpdate\": {\n                  \"partition\": 0\n               },\n               \"type\": \"RollingUpdate\"\n            },\n            \"volumeClaimTemplates\": [\n               {\n                  \"metadata\": {\n                     \"labels\": {\n                        \"name\": \"datadir\"\n                     },\n                     \"name\": \"datadir\",\n                     \"namespace\": \"foons\"\n                  },\n                  \"spec\": {\n                     \"accessModes\": [\n                        \"ReadWriteOnce\"\n                     ],\n                     \"resources\": {\n                        \"requests\": {\n                           \"storage\": \"10Gi\"\n                        }\n                     }\n                  }\n               }\n            ]\n         }\n      }\n   ],\n   \"kind\": \"List\"\n}\n"
  },
  {
    "path": "vendor_jsonnet/kube-libsonnet/tests/golden/unittests.json",
    "content": "true\n"
  },
  {
    "path": "vendor_jsonnet/kube-libsonnet/tests/test-sealedsecrets-datalines.jsonnet",
    "content": "local kube = import \"../kube.libsonnet\";\nlocal stack = {\n  sealedsecret: kube.SealedSecret(\"foo\") {\n    spec+: {\n      datalines_: importstr \"test-sealedsecrets-datalines.txt\",\n    },\n  },\n};\n\nkube.List() {\n  items_+: stack,\n}\n"
  },
  {
    "path": "vendor_jsonnet/kube-libsonnet/tests/test-sealedsecrets-datalines.txt",
    "content": "AgCz+HLrnNCZoDCOcCbMgKp//+Tfz5ecNcVGZOnLT/M/RP5E7u6ifHtm3GfG8qJKcRK9v6ZBYOIeDqenlSNMpRmigXH0p2ThMfmBcPIYfLvqOcwfwssfOS4jp02qsAjSfaAXuL2Mze15OnXImiPHsoi9BIrJnyhRObBeRMnJ4xMUAFPih1CfqY3nl7FQhH6+4t3Wh52JY3VmDWYYGH5Kc5JyS40PodCUEgId1kJKtChUPQ0JsTgj6W8ymyJ0bC8jyzrZHD0gLo26T+7mNiJyUEU9qj7p8YhZj3v6VcEv4DKQVVvkGk94svTAdnSQrh5cNOaZrp6eTJqIs+PcrelTVitoZzZsnIc+ze6aPjGhft752xa9CDmuCkQ9XkWfWpGcGrEv1jmIPkJYIjPIKgP6CS5eU+eBZNIkNpIof2jiYdqqP59BhoBwVkC/Rm1GfgOdLxO55sicTwSmRA+/pkE6hopK327uwxWmtGZ5/kUuW6nZ4shTNTsr1n1skZJvw6UfwkAgIYuDctHqw+y7eSdDM2gggLM72TgBEf5111LnIj2rroQu5bR6XVVpMy6QFhE9LLAC3kcs/BqPh5A7qq/ffZrFhWpSIS7voavtdqn6XhdJ8TsJ7Wbkhjxf1S/l/YqS6B1ZlJFlrdcNA6BHqzyjncmrD030YfwNqYUl2Itxok7S6DaImzBkxV5Uqd18EiuVcbVL3Ic1I3B4pviBAo0eG5nn4/uZCjuBKu6muLtR5GFMblbqV23MiB8Q3jtODvVw6SbFfNQx836RsJAFSmE+axSGvZPq4zDXGUYekOHZ+ov6Gd2Czv99Cf67r3ogJIzDR1uVRZH78bYrjEAkRGpKETUfLFfRyRZzWW/K5cKEAoDttzHI5grlgm44k5RQeaoargmyVjuEgjEZ2yZnl5Z7BX3YSazcLfK2t/wwGNHMMgedf3SeGETIx552CehRw7+A1+0q4iKQ9z13dPpjAYNzmLd1bGY4ORQeEf4Duk6NTXXHfpyRSq/CNK5BPhKnTn1OEnUQ+ttD/ZjNkx2OMFsX7zfMfk0RRRnKlWpanYOtTinx5WbpIYOKZ4Labb7+CnuHMgTZoNTXZe2DnNFqWIgm47U=\n"
  },
  {
    "path": "vendor_jsonnet/kube-libsonnet/tests/test-sealedsecrets.jsonnet",
    "content": "local kube = import \"../kube.libsonnet\";\nlocal stack = {\n  sealedsecret: kube.SealedSecret(\"foo\") {\n    spec+: {\n      data: \"dGVzdAo=\",\n    },\n  },\n};\n\nkube.List() {\n  items_+: stack,\n}\n"
  },
  {
    "path": "vendor_jsonnet/kube-libsonnet/tests/test-simple-validate.jsonnet",
    "content": "local bitnami = import \"../bitnami.libsonnet\";\nlocal kube = import \"../kube.libsonnet\";\n\nlocal stack = {\n  namespace:: \"foons\",\n  name:: \"foo\",\n\n  ns: kube.Namespace($.namespace),\n\n  sa: kube.ServiceAccount($.name + \"-sa\") {\n    metadata+: { namespace: $.namespace },\n  },\n\n  role: kube.Role($.name + \"-role\") {\n    metadata+: { namespace: $.namespace },\n    rules: [{\n      apiGroups: [\"\"],\n      resources: [\"pods\", \"secrets\", \"configmaps\", \"persistentvolumeclaims\"],\n      verbs: [\"get\"],\n    }, {\n      apiGroups: [\"\"],\n      resources: [\"pods\"],\n      verbs: [\"patch\"],\n    }],\n  },\n\n  rolebinding: kube.RoleBinding($.name + \"-rolebinding\") {\n    metadata+: { namespace: $.namespace },\n    roleRef_: $.role,\n    subjects_+: [$.sa],\n  },\n\n  config: kube.ConfigMap($.name + \"-config\") {\n    metadata+: { namespace: $.namespace },\n    data: {\n      foo_key: \"bar_val\",\n    },\n  },\n\n  secret: kube.Secret($.name + \"-secret\") {\n    metadata+: { namespace: $.namespace },\n    data: {\n      sec_key: \"c2VjcmV0Cg==\",\n    },\n  },\n\n  // NB: making up an Ingress pointing to $.deploy Pod\n  service: kube.Service($.name + \"-svc\") {\n    metadata+: { namespace: $.namespace },\n    target_pod: $.deploy.spec.template,\n  },\n\n  ingress: bitnami.Ingress($.name + \"-ingress\") {\n    metadata+: { namespace: $.namespace },\n    host: \"foo.g.dev.bitnami.net\",\n    target_svc: $.service,\n  },\n\n  // NB: just a simple example pod\n  pod: kube.Pod($.name + \"-pod\") {\n    metadata+: { namespace: $.namespace },\n    spec+: {\n      containers_+: {\n        foo_cont: kube.Container($.name) {\n          image: \"nginx:1.12\",\n          env_+: {\n            my_secret: kube.SecretKeyRef($.secret, \"sec_key\"),\n          },\n          ports_+: {\n            http: { containerPort: 80 },\n            udp_port: { containerPort: 888, protocol: \"UDP\" },\n          },\n          volumeMounts_+: {\n            config_vol: { mountPath: \"/config\" },\n          },\n        },\n      },\n      volumes_+: {\n        config_vol: kube.ConfigMapVolume($.config),\n      },\n    },\n  },\n\n  // NB: all object below needing to spec a Pod will just\n  // use above particular pod manifest just for convenience\n  deploy: kube.Deployment($.name + \"-deploy\") {\n    metadata+: { namespace: $.namespace },\n    spec+: {\n      template+: {\n        spec+: $.pod.spec {\n          serviceAccountName: $.sa.metadata.name,\n        },\n      },\n    },\n  },\n\n  sts: kube.StatefulSet($.name + \"-sts\") {\n    metadata+: { namespace: $.namespace },\n    spec+: {\n      template+: {\n        spec+: $.pod.spec {\n          serviceAccountName: $.sa.metadata.name,\n          containers_+: {\n            foo_cont+: {\n              volumeMounts_+: {\n                datadir: { mountPath: \"/foo/data\" },\n              },\n            },\n          },\n        },\n      },\n      volumeClaimTemplates_+: {\n        datadir: kube.PersistentVolumeClaim(\"datadir\") {\n          metadata+: { namespace: $.namespace },\n          storage: \"10Gi\",\n        },\n      },\n    },\n  },\n\n  ds: kube.DaemonSet($.name + \"-ds\") {\n    metadata+: { namespace: $.namespace },\n    spec+: {\n      template+: {\n        spec: $.pod.spec,\n      },\n    },\n  },\n\n  job: kube.Job($.name + \"-job\") {\n    metadata+: { namespace: $.namespace },\n    spec+: {\n      template+: {\n        spec+: {\n          containers_+: {\n            foo_cont: kube.Container($.name) {\n              image: \"busybox\",\n            },\n          },\n        },\n      },\n    },\n  },\n\n  cronjob: kube.CronJob($.name + \"-cronjob\") {\n    metadata+: { namespace: $.namespace },\n    spec+: {\n      jobTemplate+: {\n        spec+: {\n          template+: {\n            spec+: {\n              containers_+: {\n                foo_cont: kube.Container($.name) {\n                  image: \"busybox\",\n                },\n              },\n            },\n          },\n        },\n      },\n      schedule: \"0 * * * *\",\n    },\n  },\n\n  // NB: create NSP from $.deploy Pod ref\n  nsp_pods: kube.NetworkPolicy($.name + \"-nsp-pods\") {\n    metadata+: { namespace: $.namespace },\n    // NB: $.deploy has unique \"foo-deploy\" label (as well as other\n    // podLabelsSelector() arg)\n    spec+: kube.podLabelsSelector($.deploy) {\n      // NB: making up $.deploy needing to get reached by $job, $.cronjob\n      // and nginx-ingress-controller (running in its own NS named \"nginx-ingress\"\n      ingress_: {\n        from_jobs_and_ingressctl: {\n          from: [\n            kube.podLabelsSelector($.job),\n            kube.podLabelsSelector($.cronjob),\n            { namespaceSelector: { matchLabels: { name: \"nginx-ingress\" } } },\n          ],\n          ports: kube.podsPorts([$.deploy]),\n        },\n      },\n      // NB: making up $.deploy needing to connect to $.sts, and\n      // \"kube-system\" NS for DNS services\n      egress_: {\n        to_sts: {\n          to: [\n            kube.podLabelsSelector($.sts),\n          ],\n          ports: kube.podsPorts([$.sts]),\n        },\n        to_kube_dns: {\n          to: [\n            { namespaceSelector: { matchLabels: { name: \"kube-system\" } } },\n          ],\n          ports: [{ port: 53, protocol: \"UDP\" }],\n        },\n      },\n    },\n  },\n};\n\nkube.List() {\n  items_+: stack,\n}\n"
  },
  {
    "path": "vendor_jsonnet/kube-libsonnet/tests/unittests.jsonnet",
    "content": "local kube = import \"../kube.libsonnet\";\n\nlocal an_obj = kube._Object(\"v1\", \"Gentle\", \"foo\");\nlocal a_pod = kube.Pod(\"foo\") {\n  metadata+: { labels+: { foo: \"bar\", bar: \"qxx\" } },\n  spec+: {\n    containers_+: {\n      foo: kube.Container(\"foo\") {\n        image: \"nginx\",\n        ports_: {\n          http: { containerPort: 8080 },\n          https: { containerPort: 8443 },\n          udp: { containerPort: 5353, protocol: \"UDP\" },\n        },\n      },\n    },\n  },\n};\nlocal a_deploy = kube.Deployment(\"foo\") {\n  spec+: { template+: { metadata+: a_pod.metadata, spec+: a_pod.spec } },\n};\n// Basic unittesting for methods that are not exercised by the other e2e-ish tests\nstd.assertEqual(kube.objectValues({ a: 1, b: 2 }), [1, 2]) &&\nstd.assertEqual(kube.objectItems({ a: 1, b: 2 }), [[\"a\", 1], [\"b\", 2]]) &&\nstd.assertEqual(kube.hyphenate(\"foo_bar_baz\"), (\"foo-bar-baz\")) &&\nstd.assertEqual(kube.mapToNamedList({ foo: { a: \"b\" } }), [{ name: \"foo\", a: \"b\" }]) &&\nstd.assertEqual(kube.filterMapByFields({ a: 1, b: 2, c: 3 }, [\"a\", \"c\", \"d\"]), { a: 1, c: 3 }) &&\nstd.assertEqual(kube.parseOctal(\"755\"), 493) &&\nstd.assertEqual(kube.siToNum(\"42G\"), 42 * 1e9) &&\nstd.assertEqual(kube.siToNum(\"42Gi\"), 42 * std.pow(2, 30)) &&\nstd.assertEqual(kube.toUpper(\"ForTy 2\"), \"FORTY 2\") &&\nstd.assertEqual(kube.toLower(\"ForTy 2\"), \"forty 2\") &&\nstd.assertEqual(an_obj, {\n  apiVersion: \"v1\",\n  kind: \"Gentle\",\n  metadata: { name: \"foo\", labels: { name: \"foo\" }, annotations: {} },\n}) &&\nstd.assertEqual(\n  [kube.podRef(a_deploy).spec.ports(\"TCP\"), kube.podRef(a_deploy).spec.ports(\"UDP\")],\n  [[8080, 8443], [5353]]\n) &&\nstd.assertEqual(\n  // latest kubecfg produces stable output from maps hashes, so below shouldn't be flaky\n  kube.podsPorts([a_deploy]),\n  [\n    { port: 8080, protocol: \"TCP\" },\n    { port: 8443, protocol: \"TCP\" },\n    { port: 5353, protocol: \"UDP\" },\n  ]\n) &&\nstd.assertEqual(\n  kube.podLabelsSelector(a_deploy),\n  { podSelector: { matchLabels: { name: \"foo\", foo: \"bar\", bar: \"qxx\" } } }\n)\n"
  },
  {
    "path": "versions.env",
    "content": "GO_VERSION=1.26.1\nGO_VERSION_LIST=\"[\\\"$GO_VERSION\\\"]\"\n"
  }
]