[
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# You may wish to alter this file to override the set of languages analyzed,\n# or to provide custom queries or build logic.\n#\n# ******** NOTE ********\n# We have attempted to detect the languages in your repository. Please check\n# the `language` matrix defined below to confirm you have the correct set of\n# supported CodeQL languages.\n#\nname: \"CodeQL\"\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [ master ]\n  schedule:\n    - cron: '26 9 * * 0'\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [ 'java' ]\n        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]\n        # Learn more about CodeQL language support at https://git.io/codeql-language-support\n\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v2\n\n    - name: Set up JDK 17\n      uses: actions/setup-java@v3\n      with:\n        distribution: 'zulu'\n        java-version: 17\n        cache: 'maven'\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v2\n      with:\n        languages: ${{ matrix.language }}\n        # If you wish to specify custom queries, you can do so here or in a config file.\n        # By default, queries listed here will override any specified in a config file.\n        # Prefix the list here with \"+\" to use these queries and those in the config file.\n        # queries: ./path/to/local/query, your-org/your-repo/queries@main\n\n    - run: mvn -B package --file pom.xml -Dspring.profiles.active=test -DskipTests\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v2\n"
  },
  {
    "path": ".github/workflows/maven.yaml",
    "content": "name: Java CI\n\non: [push]\n\njobs:\n  test:\n    name: \"Test\"\n\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        kubernetes_version:\n          - \"kindest/node:v1.29.1@sha256:a0cc28af37cf39b019e2b448c54d1a3f789de32536cb5a5db61a49623e527144\"\n          - \"kindest/node:v1.28.6@sha256:b7e1cf6b2b729f604133c667a6be8aab6f4dde5bb042c1891ae248d9154f665b\"\n          - \"kindest/node:v1.27.10@sha256:3700c811144e24a6c6181065265f69b9bf0b437c45741017182d7c82b908918f\"\n          - \"kindest/node:v1.26.13@sha256:15ae92d507b7d4aec6e8920d358fc63d3b980493db191d7327541fbaaed1f789\"\n\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Set up JDK 17\n        uses: actions/setup-java@v3\n        with:\n          distribution: 'zulu'\n          java-version: 17\n          cache: 'maven'\n\n      - uses: helm/kind-action@v1.5.0\n        with:\n          version: \"v0.17.0\"\n          node_image: \"${{ matrix.kubernetes_version }}\"\n\n      - name: \"Kubernetes version\"\n        run: |\n          kubectl version\n\n      - name: \"Create Custom Resource\"\n        run: |\n          kubectl apply -f crd.yml\n\n      - name: Build with Maven\n        run: mvn -B package --file pom.xml -Dspring.profiles.active=test\n\n  docker-push:\n    name: Docker Push (GHCR & public ECR)\n    runs-on: ubuntu-latest\n    if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/docker-')\n    permissions:\n      id-token: write\n      contents: read\n      packages: write\n    needs: test\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v1\n\n      - name: Set output\n        id: vars\n        run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}\n\n      - name: Docker publish\n        uses: daspawnw/docker-multi-build-push-action@master\n        with:\n          platforms: \"linux/amd64,linux/arm64\"\n          docker-tag: \"${{ steps.vars.outputs.tag }}\"\n          ghcr-enabled: \"true\"\n          ghcr-token: \"${{ secrets.GITHUB_TOKEN }}\"\n          ecr-enabled: ${{ github.repository == 'daspawnw/vault-crd' }}\n          ecr-role-to-assume: \"${{ secrets.AWS_PUBLIC_ECR_ARN }}\"\n          ecr-repository-url: \"public.ecr.aws/l2l6k4u5/vault-crd\"\n"
  },
  {
    "path": ".gitignore",
    "content": "target/*\n.idea/*\n*.iml\nvault/*"
  },
  {
    "path": ".mvn/wrapper/maven-wrapper.properties",
    "content": "distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.3/apache-maven-3.5.3-bin.zip\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM gcr.io/distroless/java17-debian11:nonroot AS SECURITY\nFROM openjdk:17 AS BUILD\n\nCOPY . /opt\nWORKDIR /opt\nRUN ./mvnw clean install -DskipTests\n\nENV JAVA_RANDOM=\"file:/dev/./urandom\"\n\nCOPY --from=SECURITY /etc/java-17-openjdk/security/java.security /java.security\nRUN echo \"networkaddress.cache.ttl=60\" >> /java.security\nRUN sed -i -e \"s@^securerandom.source=.*@securerandom.source=${JAVA_RANDOM}@\" /java.security\n\nFROM gcr.io/distroless/java17-debian11:nonroot\n\nCOPY --from=BUILD /opt/target/vault-crd.jar /opt/vault-crd.jar\nCOPY --from=BUILD /java.security /etc/java-17-openjdk/security/java.security\n\nENTRYPOINT [\"/usr/bin/java\", \"-Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts\", \"-Djavax.net.ssl.trustStorePassword=changeit\", \"-Djavax.net.ssl.trustStoreType=jks\", \"-Dkeystore.pkcs12.legacy\"]\nCMD [\"-jar\", \"/opt/vault-crd.jar\"]\n\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\" files 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 files 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 files 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 files 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": "crd.yml",
    "content": "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: vault.koudingspawn.de\nspec:\n  group: koudingspawn.de\n  scope: Namespaced\n  names:\n    plural: vault\n    singular: vault\n    kind: Vault\n    shortNames:\n      - vt\n  versions:\n    - name: v1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          properties:\n            spec:\n              type: object\n              properties:\n                path:\n                  type: string\n                  pattern: '^.*?\\/.*?(\\/.*?)?$'\n                type:\n                  type: string\n                  enum:\n                    - PKI\n                    - PKIJKS\n                    - CERT\n                    - CERTJKS\n                    - DOCKERCFG\n                    - KEYVALUE\n                    - KEYVALUEV2\n                    - PROPERTIES\n                pkiConfiguration:\n                  type: object\n                  properties:\n                    commonName:\n                      type: string\n                    altNames:\n                      type: string\n                    ipSans:\n                      type: string\n                    ttl:\n                      type: string\n                      pattern: '^[0-9]{1,}[hm]$'\n                jksConfiguration:\n                  type: object\n                  properties:\n                    password:\n                      type: string\n                    alias:\n                      type: string\n                    keyName:\n                      type: string\n                    caAlias:\n                      type: string\n                versionConfiguration:\n                  type: object\n                  properties:\n                    version:\n                      type: integer\n                propertiesConfiguration:\n                  type: object\n                  properties:\n                    context:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    files:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                dockerCfgConfiguration:\n                  type: object\n                  properties:\n                    type:\n                      type: string\n                      enum:\n                        - KEYVALUE\n                        - KEYVALUEV2\n                    version:\n                      type: integer\n                changeAdjustmentCallback:\n                  type: object\n                  properties:\n                    type:\n                      type: string\n                    name:\n                      type: string\n              required:\n                - type"
  },
  {
    "path": "deploy/admission-webhook.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: vault-crd\n  namespace: vault-crd\nspec:\n  selector:\n    app: vault-crd\n  ports:\n    - port: 8080\n  type: ClusterIP\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  labels:\n    app: vault-crd\n  name: vault-crd-admission\nwebhooks:\n  - name: validate.vault.koudingspawn.de\n    admissionReviewVersions: [\"v1\"]\n    sideEffects: None\n    rules:\n      - apiGroups:\n          - koudingspawn.de\n        apiVersions:\n          - v1\n        operations:\n          - CREATE\n          - UPDATE\n        resources:\n          - vault\n    failurePolicy: Fail\n    clientConfig:\n      service:\n        namespace: vault-crd\n        name: vault-crd\n        path: /validation/vault-crd\n        port: 8080\n      caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURRVENDQWltZ0F3SUJBZ0lVUDk4cG9lNXF0TVExWXhrVS85eHc5ZGp4N05Bd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0dqRVlNQllHQTFVRUF4TVBhMjkxWkdsdVozTndZWGR1TG1SbE1CNFhEVEl3TURZeU5ERTVNVEF4TkZvWApEVE13TURZeU1qRTVNVEEwTkZvd0dqRVlNQllHQTFVRUF4TVBhMjkxWkdsdVozTndZWGR1TG1SbE1JSUJJakFOCkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXNWbklIY2JCeHl2NmpOS3V5YkZpMFNkbEtONlAKU1U2RlJOUzJwQ1NNK3R3eWtCblllZUtacTMxQmZYQW5EMlVrR1dod0gwWHF1QmJ4S3Bsa2lYWVVMT25qTFQwLwpWVG5LN1U2NWFWZ0VMZlZJVFNHRnJFcjMwdTdYbWN5NkNWZmkzd25CMjZkZnR1V2NSWlFoaXIxZUE4VUZjejBQCkxlTWhiSTRybENWcnU2bHFFTzl0bGRId21ScGc5dWYyYnJiTi9PNDlaSUtYVGRBSW5jVTVacnV3d21MOVpnbUIKc2tzaDBvZWFkaXpMbzRuSmNjdVZYQjlwMHJjcjBPdG5qSHo1SGdCUElzTDB6UVZMUzVSZ01CTkxDbTVnZjlrcAp4S1UzQ0ZnU0Z0Vng5WlE3aG9hUEl3VnRqWUNrcCtzQVBycjBQNkk0Um95c2U3RDB2UERQUCs5NDF3SURBUUFCCm8zOHdmVEFPQmdOVkhROEJBZjhFQkFNQ0FRWXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QWRCZ05WSFE0RUZnUVUKa0hrOXBHMkg0eTJac0ZEUVZlNGJVTGhoMCtrd0h3WURWUjBqQkJnd0ZvQVVrSGs5cEcySDR5MlpzRkRRVmU0YgpVTGhoMCtrd0dnWURWUjBSQkJNd0VZSVBhMjkxWkdsdVozTndZWGR1TG1SbE1BMEdDU3FHU0liM0RRRUJDd1VBCkE0SUJBUUFoMW9pMy9iRW5Lb1Rxdkt2NEZZeVJKVDQzMkIxYkp2MG94ZERLaFJndVowYmQ1WXVOMHBnNTcxL2QKb1UvVUN6ellzaUVYZmw3NHREUndNWVUveXhlQVJKQ3B2RWswcVhOdHJlS1hZL0dDU0wxbjlKU1dTMk1xVDBBeQpuTWxqdEkrd3R5Ujh2MW05SnppNFdFNHdWdVRuclhlb1BSeVpKR0F1Q2xRbnk2VW5Fei9LS3ZvQ0pCQW1UY1NKCk5hZkNwYUh3b25sQVp5bXZRN0JQZHZoMk52ckdQazk2aEVZc1lnUVl6VW5KcURoT0Z2RWF1MjRLeEk2NlpXUnIKMGlSNkZmTTQ2ZjBNRVdqdmRSUGRucHh2dDhPbTBiNjVER0czc2hVTHNLZWJENFI4YjdCeTdrVUV0U1FkNVkzcwpHK1k5RTdpbXdWR1hlUTh1eWw3ZGNSV1AwTkJ1Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K\n"
  },
  {
    "path": "deploy/rbac.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: vault-crd\n\n---\n\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: vault-crd-serviceaccount\n  namespace: vault-crd\n\n---\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: vault-crd-clusterrole\nrules:\n  - apiGroups:\n    - apiextensions.k8s.io\n    resources:\n    - customresourcedefinitions\n    verbs:\n    - get\n  - apiGroups:\n      - \"koudingspawn.de\"\n    resources:\n      - vault\n    verbs:\n      - list\n      - watch\n      - get\n  - apiGroups:\n      - \"\"\n    resources:\n      - secrets\n      - events\n    verbs:\n      - get\n      - create\n      - patch\n      - update\n      - delete\n      - watch\n      - list\n  - apiGroups:\n      - extensions\n      - apps\n    resources:\n      - deployments\n    verbs:\n      - update\n      - get\n      - patch\n\n---\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: vault-crd-clusterrole-binding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: vault-crd-clusterrole\nsubjects:\n  - kind: ServiceAccount\n    name: vault-crd-serviceaccount\n    namespace: vault-crd\n\n---\n\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: vault.koudingspawn.de\nspec:\n  group: koudingspawn.de\n  scope: Namespaced\n  names:\n    plural: vault\n    singular: vault\n    kind: Vault\n    shortNames:\n      - vt\n  versions:\n    - name: v1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          properties:\n            spec:\n              type: object\n              properties:\n                path:\n                  type: string\n                  pattern: '^.*?\\/.*?(\\/.*?)?$'\n                type:\n                  type: string\n                  enum:\n                    - PKI\n                    - PKIJKS\n                    - CERT\n                    - CERTJKS\n                    - DOCKERCFG\n                    - KEYVALUE\n                    - KEYVALUEV2\n                    - PROPERTIES\n                pkiConfiguration:\n                  type: object\n                  properties:\n                    commonName:\n                      type: string\n                    altNames:\n                      type: string\n                    ipSans:\n                      type: string\n                    ttl:\n                      type: string\n                      pattern: '^[0-9]{1,}[hm]$'\n                jksConfiguration:\n                  type: object\n                  properties:\n                    password:\n                      type: string\n                    alias:\n                      type: string\n                    keyName:\n                      type: string\n                    caAlias:\n                      type: string\n                versionConfiguration:\n                  type: object\n                  properties:\n                    version:\n                      type: integer\n                propertiesConfiguration:\n                  type: object\n                  properties:\n                    context:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                    files:\n                      type: object\n                      x-kubernetes-preserve-unknown-fields: true\n                dockerCfgConfiguration:\n                  type: object\n                  properties:\n                    type:\n                      type: string\n                      enum:\n                        - KEYVALUE\n                        - KEYVALUEV2\n                    version:\n                      type: integer\n                changeAdjustmentCallback:\n                  type: object\n                  properties:\n                    type:\n                      type: string\n                    name:\n                      type: string\n              required:\n                - type\n\n---\n\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app: vault-crd\n  name: vault-crd\n  namespace: vault-crd\nspec:\n  selector:\n    matchLabels:\n      app: vault-crd\n  replicas: 1\n  template:\n    metadata:\n      labels:\n        app: vault-crd\n    spec:\n      serviceAccountName: vault-crd-serviceaccount\n#      initContainers:\n#        - name: convert-https\n#          image: shamelesscookie/openssl:1.1.1g\n#          command:\n#            - /bin/bash\n#          args:\n#            - \"-c\"\n#            - \"openssl pkcs12 -export -in /opt/certificate/tls.crt -inkey /opt/certificate/tls.key -out /opt/target/keystore.p12 -passout pass:changeit -name admission-tls\"\n#          volumeMounts:\n#            - mountPath: /opt/certificate\n#              name: pem-cert\n#            - mountPath: /opt/target\n#              name: pkcs12-cert\n      containers:\n      - name: vault-crd\n        image: daspawnw/vault-crd:1.11.0\n        env:\n        - name: KUBERNETES_VAULT_URL\n          value: \"http://localhost:8080/v1/\"\n        - name: KUBERNETES_VAULT_TOKEN\n          valueFrom:\n            secretKeyRef:\n              name: vault-token\n              key: token\n#        - name: SERVER_SSL_KEY-STORE-TYPE\n#          value: PKCS12\n#        - name: SERVER_SSL_KEY-STORE\n#          value: \"/opt/certificate/keystore.p12\"\n#        - name: SERVER_SSL_KEY-STORE-PASSWORD\n#          value: changeit\n#        - name: SERVER_SSL_KEY-ALIAS\n#          value: \"admission-tls\"\n        ports:\n          - containerPort: 8080\n        livenessProbe:\n          httpGet:\n            port: 8080\n            path: \"/actuator/health\"\n#            scheme: HTTPS\n          initialDelaySeconds: 30\n          failureThreshold: 3\n          periodSeconds: 30\n          successThreshold: 1\n          timeoutSeconds: 5\n#        volumeMounts:\n#          - mountPath: /opt/certificate\n#            name: pkcs12-cert\n#      volumes:\n#        - name: pem-cert\n#          secret:\n#            secretName: vault-crd-tls\n#        - name: pkcs12-cert\n#          emptyDir: {}\n      restartPolicy: Always\n---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: vault-token\n  namespace: vault-crd\ndata:\n  token: \"cm9vdA==\"\n---\n#apiVersion: v1\n#kind: Secret\n#metadata:\n#  name: vault-crd-tls\n#  namespace: vault-crd\n#data:\n#  tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURjakNDQWxxZ0F3SUJBZ0lVY3d6Z0hrdjRhOXpHOE5hQm1rbVFPdGxZM3hjd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0dqRVlNQllHQTFVRUF4TVBhMjkxWkdsdVozTndZWGR1TG1SbE1CNFhEVEl3TURZeU5ERTVNVEExTUZvWApEVEl3TURjeU5qRTVNVEV5TUZvd0hqRWNNQm9HQTFVRUF4TVRkbUYxYkhRdFkzSmtMblpoZFd4MExXTnlaRENDCkFTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTG84Z2lWMjQ5UTArazMvenFnY2xVN3oKTisyT2N1VDNERG5JbXZaYTNiOXZGYjRXNVhOaXlpT0xtbHhnMDB3RGkyV01DcEI1RldmRUQwQU5KQUpWMEdRdwowVmlFTG5TVDRJYTRUcTlxUWlvc0J0RXd5Vkx1QkZSU1RHTDJiSUV5K3dBaGlrQ3dmbEI2L2trN05VbHlXZG8rCkRtcUorbDQ4RnVkbytpdTJyYkxtR0lnMTFDTy8rUTJlRnZCaTBaTTZEUzliUVNYOUYxTmMxdFZyNndtSWNOTHQKNzRHT2JQaG5rbFBaQjMrUzRFTk8xbHhzcCtkNGw0QkZEYWJWMHdCdWVmaFdqdktMSlZNRzlOWWZkWjdBaXArZwpYWWhpYVpPQS9xTlhSTDQzWlY1OXBBS2NtNWlFdUFLMStrYVBuWUdYQUtRRFE3L29hSlJwbEVqT1VLVHRObDBDCkF3RUFBYU9CcXpDQnFEQU9CZ05WSFE4QkFmOEVCQU1DQTZnd0hRWURWUjBsQkJZd0ZBWUlLd1lCQlFVSEF3RUcKQ0NzR0FRVUZCd01DTUIwR0ExVWREZ1FXQkJRNjFvN2cwVllHc2pZZTJ2bkVKQ29LTElvYnREQWZCZ05WSFNNRQpHREFXZ0JTUWVUMmtiWWZqTFptd1VOQlY3aHRRdUdIVDZUQTNCZ05WSFJFRU1EQXVnaE4yWVhWc2RDMWpjbVF1CmRtRjFiSFF0WTNKa2doZDJZWFZzZEMxamNtUXVkbUYxYkhRdFkzSmtMbk4yWXpBTkJna3Foa2lHOXcwQkFRc0YKQUFPQ0FRRUFSVTVud05rb24xRWdxZWVQNnRHZzZiNmY5TC94cnd5bHFndURyWVJOUmIyeGl1ZTV0UDREZFNVZAo2eXBVNXdsWGdHTmlmcHBpeVhSbFlIZUtIT09jRUtlTlByb25KYnBaa25Wb3ZpaVU2aWhJNFBLM3lRRW51N2twCjhjdldxVDh2WVVJa2VwV0dEd2NYRTRNNWlSelJ5VXVYSkxXZk4yRnJjTlJ1RUdEL3JKRFlwQ00rTDNDd0U5TFgKbmFCaDRJTG1HZjJmMHpZaVZMWTN0bTF4c1E2ZGZCQlZMMHZDZmlBVzlKYkNGbTVSOXRySWttcXdBY09lQTFGUApMdExUNjJ5OHRZbjFFK0h6ZTVneVRBdXpoRHhHbmxXRGtkRDJVSXMrbUxqcWxOVzZrcTI0NHFJOUdZZ3drVFJiCm1DOURyZ2lFRWFIQi9yN2lBZVNaWHZkUzg2ckVpZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K\n#  tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBdWp5Q0pYYmoxRFQ2VGYvT3FCeVZUdk0zN1k1eTVQY01PY2lhOWxyZHYyOFZ2aGJsCmMyTEtJNHVhWEdEVFRBT0xaWXdLa0hrVlo4UVBRQTBrQWxYUVpERFJXSVF1ZEpQZ2hyaE9yMnBDS2l3RzBUREoKVXU0RVZGSk1ZdlpzZ1RMN0FDR0tRTEIrVUhyK1NUczFTWEpaMmo0T2FvbjZYandXNTJqNks3YXRzdVlZaURYVQpJNy81RFo0VzhHTFJrem9OTDF0QkpmMFhVMXpXMVd2ckNZaHcwdTN2Z1k1cytHZVNVOWtIZjVMZ1EwN1dYR3luCjUzaVhnRVVOcHRYVEFHNTUrRmFPOG9zbFV3YjAxaDkxbnNDS242QmRpR0pwazREK28xZEV2amRsWG4ya0FweWIKbUlTNEFyWDZSbytkZ1pjQXBBTkR2K2hvbEdtVVNNNVFwTzAyWFFJREFRQUJBb0lCQUh5TjhXRUxGYTZjUy9lVQpxV3NIeXRnRmxKY2RtVHdHK2pjL01seW5RdjFBVnlOTi91RmY1ZDlHQTlQYXNoWjVuR1lxOWZuUDhYLzN3VmRPCk1wSVpRSWx4bU9HQmJleHI1bE5UdXRSWTFhMk15blpvRVkyVVFITUFvN1BnS1l0elJDbS9STTZrKzZYcHpGMi8KNnBDWG1QNThXSG5xay9jb2F3MFR5WlVvMVJ6NjMvemxSYXJBMXdqQVVGMDNJRElNTFlzYWxCZld5Nm9neEhHZQpuOC95Z1E4cyt5Z29UY3FZb1hHYnZ2a01IOVB6b1oyZko3RWlrTW9Cb29jcjRxbjZZYUFIb3BJR0dMYmM5MFdsCmZVUHBheEtkcDMzUTlXK1MzbU11bm9zUk9MbHdqMS9HTHhaKzJWRkZqUTJseHpBOGdqOW1SV0tabzhQYnVHUVEKU0p4cFhVRUNnWUVBNnZhWDFQSm4yUUNxeCtET3k4SnZFNWp6WVBGcTlFR0FpRFg5VENTb3R4U1d2OTVOUVh0MQpBR1d1RmVETE1YSmZqejhibldwdVVuVzRqK0tNRnRaUDNMZ1Nmd3ZTUnVnNGVsQ1RHVEllWmJmQ0pqTGlrNVNPCjJ6SVBiK2xtL3Z3eXU5V3NkZEh3OVIyUjc3eWlzWkkxZlBDMndTTzZzRjlxT21rL2t1RGZibzBDZ1lFQXl1a1YKcjI3andnM2FpMEJLdTNhcmhSRExqbklzSnF5Qy9yUVJEUzlVQjVuTks1L1l4K1o4S0R5M2toMmV2YXNic1Q0cgozcFUxQXdKOGZDQnRXaC93YXRVYlNRZkNOQ0ppZ2hsbUpYcTJDeEVRUUllN1d5MGpNeWpINzJodVgwMGY0dFJWCkt1clVpK2V0SjA3NHBvbHNUaG9DUnZqbzdyaHREY3R3UmM2bUd4RUNnWUVBdUNoS1hJY1p5Y1Z5RlhNbjRpQWsKdXpGNElCVllCTldLRGpoeXJVbFdTeGlDQnlRUFhUR01SS0Z0VG94LzllTjA3bXRDRTZFbGtzL2R0amlVSUJvZApRaHVyczVQcVhkVUkzeVZrQmExNGtiVHpJTWxsT05LSkhWZ2hMVSs4Z0VIZTZjWFJoQTdtVXRlNFdEUjdONzRtCjJpUTR1U3h0MkdzUWNYT29kbEIyRHNrQ2dZRUF5VDZwZ2toaDNlbnRrZVNlK2hSMWd0RW9na3ZjWERNRzdPVGMKY0k0N01ocXBjWlhrNUVaRlo0Ym9yaU53ZUQ3SGhWL2JGTFE1VXBYWnJ5WmVMbCsxQzgvMmN0VWVHS1R0dklqQwpWWFBDTDNHcUE4WmEzTkFFdEUzREZrQW1ENkVuZWNvTCtqZlR2RHAzOHArUlgySzJwek9HaEt1RUlwZUptWC9uCkIyVXdPM0VDZ1lFQTRYK25CRkRsNEdXbFNQU2VKZzZ4L3JWUXFyZUkwcmIrSUViNGhGOVZBcndEc2Z6L0hnb3cKSGpDTkR0N2t0YlJVTzlsNTJaZ3YrbWlOS3Z5dnNCbUNQalA5d0k4Y2hUZlhOZ1ZIYzE5YzJLTWRxTkdIckw1YwpTdytHMkl3MFRjREpQeFZyNGxkN01NNFZtYmtIKzBUVEtyVThLWkZQc2o2RGYraS9mMSs0NEE0PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=\n"
  },
  {
    "path": "examples/cert.yml",
    "content": "apiVersion: \"koudingspawn.de/v1\"\nkind: Vault\nmetadata:\n  name: test-cert\nspec:\n  path: \"keyvaluev1/vault.koudingspawn.de\"\n  type: \"CERT\"\n"
  },
  {
    "path": "examples/certjks-rollout-redo.yml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: nginx\n  labels:\n    app: nginx\nspec:\n  replicas: 1\n  template:\n    metadata:\n      name: nginx\n      labels:\n        app: nginx\n    spec:\n      containers:\n        - name: nginx\n          image: nginx:latest\n          imagePullPolicy: IfNotPresent\n      restartPolicy: Always\n  selector:\n    matchLabels:\n      app: nginx\n\n\n---\napiVersion: \"koudingspawn.de/v1\"\nkind: Vault\nmetadata:\n  name: test-rollout-redo-certjks\nspec:\n  path: \"keyvaluev1/vault.koudingspawn.de\"\n  type: \"CERTJKS\"\n  changeAdjustmentCallback:\n    type: deployment\n    name: nginx"
  },
  {
    "path": "examples/certjks.yml",
    "content": "apiVersion: \"koudingspawn.de/v1\"\nkind: Vault\nmetadata:\n  name: test-certjks\nspec:\n  path: \"keyvaluev1/vault.koudingspawn.de\"\n  type: \"CERTJKS\"\n"
  },
  {
    "path": "examples/dockercfg-error.yml",
    "content": "apiVersion: \"koudingspawn.de/v1\"\nkind: Vault\nmetadata:\n  name: test-dockercfg-error\nspec:\n  path: \"blub/docker-hub\"\n  type: \"DOCKERCFG\"\n"
  },
  {
    "path": "examples/dockercfg.yml",
    "content": "apiVersion: \"koudingspawn.de/v1\"\nkind: Vault\nmetadata:\n  name: test-dockercfg\nspec:\n  path: \"keyvaluev1/docker-hub\"\n  type: \"DOCKERCFG\"\n"
  },
  {
    "path": "examples/keyvalue.yml",
    "content": "apiVersion: \"koudingspawn.de/v1\"\nkind: Vault\nmetadata:\n  name: test-keyvalue\nspec:\n  path: \"keyvaluev1/docker-hub\"\n  type: \"KEYVALUE\"\n"
  },
  {
    "path": "examples/keyvaluev2-version.yml",
    "content": "apiVersion: \"koudingspawn.de/v1\"\nkind: Vault\nmetadata:\n  name: test-keyvaluev2\nspec:\n  path: \"keyvaluev2/example\"\n  type: \"KEYVALUEV2\"\n  versionConfiguration:\n    version: 2"
  },
  {
    "path": "examples/keyvaluev2.yml",
    "content": "apiVersion: \"koudingspawn.de/v1\"\nkind: Vault\nmetadata:\n  name: test-keyvaluev2\nspec:\n  path: \"keyvaluev2/example\"\n  type: \"KEYVALUEV2\""
  },
  {
    "path": "examples/kind/cluster.yaml",
    "content": "kind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n  - role: control-plane\n    extraPortMappings:\n      - containerPort: 30078\n        hostPort: 8200"
  },
  {
    "path": "examples/kind/run.sh",
    "content": "#!/usr/bin/env bash\n\n### setup kind cluster\nkind create cluster --config $PWD/cluster.yaml\n### it exposes at 8200 a port for vault\n\n\n### install vault with a static token\nkind get kubeconfig > ~/.kube/kind_config\nexport KUBECONFIG=\"$HOME/.kube/kind_config\"\n\nkubectl create namespace vault\nkubectl apply -f vault.yaml --namespace vault\n\nwhile [[ \"$(curl -s -o /dev/null -w ''%{http_code}'' localhost:8200/ui/)\" != \"200\" ]]; do sleep 5; done\necho \"Vault is up and running\"\n\nexport VAULT_ADDR=\"http://localhost:8200\"\nexport VAULT_TOKEN=\"root\"\n### end: install vault with a static token\n\n### deploy vault-crd\nkubectl apply -f ../../deploy/rbac.yaml\nkubectl apply -f ../../deploy/admission-webhook.yaml\n### end: deploy vault-crd\n\n### configure vault\nvault secrets enable -version=1 --path=keyvaluev1 kv\n\necho \"Configure vault with default values\"\nvault write keyvaluev1/docker-hub url=registry.gitlab.com username=username password=VERYSECURE email=john.doe@test.com\n\nvault secrets enable -path=testpki -description=testpki pki\nvault secrets tune -max-lease-ttl=8760h testpki\nvault write testpki/root/generate/internal \\\n      common_name=koudingspawn.de \\\n      ttl=8500h\nvault write testpki/roles/testrole \\\n      allowed_domains=koudingspawn.de \\\n      allow_subdomains=true \\\n      max_ttl=200h\n\nvault write -format=json testpki/issue/testrole common_name=vault.koudingspawn.de > data.json\nvault write keyvaluev1/vault.koudingspawn.de @data.json\nrm data.json\n\nvault secrets enable -version=2 --path=keyvaluev2 kv\nvault kv put keyvaluev2/example key=first-version value=first-version\nvault kv put keyvaluev2/example key=second-version value=second-version\nvault kv put keyvaluev2/example key=third-version value=third-version\nvault kv put keyvaluev2/example key=fourth-version value=fourth-version\n\nvault kv put keyvaluev2/database/root username=root password=really\nvault write keyvaluev1/database/host host=localhost\n### end: configure vault"
  },
  {
    "path": "examples/kind/vault.yaml",
    "content": "---\n# Source: vault/templates/server-serviceaccount.yaml\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: vault\n  namespace: vault\n  labels:\n    helm.sh/chart: vault-0.6.0\n    app.kubernetes.io/name: vault\n    app.kubernetes.io/instance: vault\n    app.kubernetes.io/managed-by: Helm\n---\n# Source: vault/templates/server-clusterrolebinding.yaml\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: vault-server-binding\n  labels:\n    helm.sh/chart: vault-0.6.0\n    app.kubernetes.io/name: vault\n    app.kubernetes.io/instance: vault\n    app.kubernetes.io/managed-by: Helm\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: system:auth-delegator\nsubjects:\n- kind: ServiceAccount\n  name: vault\n  namespace: vault\n---\n# Source: vault/templates/server-headless-service.yaml\n# Service for Vault cluster\napiVersion: v1\nkind: Service\nmetadata:\n  name: vault-internal\n  namespace: vault\n  labels:\n    helm.sh/chart: vault-0.6.0\n    app.kubernetes.io/name: vault\n    app.kubernetes.io/instance: vault\n    app.kubernetes.io/managed-by: Helm\n  annotations:\n    service.alpha.kubernetes.io/tolerate-unready-endpoints: \"true\"\n\nspec:\n  clusterIP: None\n  publishNotReadyAddresses: true\n  ports:\n    - name: \"http\"\n      port: 8200\n      targetPort: 8200\n    - name: https-internal\n      port: 8201\n      targetPort: 8201\n  selector:\n    app.kubernetes.io/name: vault\n    app.kubernetes.io/instance: vault\n    component: server\n---\n# Source: vault/templates/server-service.yaml\n# Service for Vault cluster\napiVersion: v1\nkind: Service\nmetadata:\n  name: vault\n  namespace: vault\n  labels:\n    helm.sh/chart: vault-0.6.0\n    app.kubernetes.io/name: vault\n    app.kubernetes.io/instance: vault\n    app.kubernetes.io/managed-by: Helm\n  annotations:\n    # This must be set in addition to publishNotReadyAddresses due\n    # to an open issue where it may not work:\n    # https://github.com/kubernetes/kubernetes/issues/58662\n    service.alpha.kubernetes.io/tolerate-unready-endpoints: \"true\"\n\nspec:\n  type: NodePort\n  # We want the servers to become available even if they're not ready\n  # since this DNS is also used for join operations.\n  publishNotReadyAddresses: true\n  ports:\n    - name: http\n      port: 8200\n      targetPort: 8200\n      nodePort: 30078\n    - name: https-internal\n      port: 8201\n      targetPort: 8201\n  selector:\n    app.kubernetes.io/name: vault\n    app.kubernetes.io/instance: vault\n    component: server\n---\n# Source: vault/templates/server-statefulset.yaml\n# StatefulSet to run the actual vault server cluster.\napiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: vault\n  namespace: vault\n  labels:\n    app.kubernetes.io/name: vault\n    app.kubernetes.io/instance: vault\n    app.kubernetes.io/managed-by: Helm\nspec:\n  serviceName: vault-internal\n  podManagementPolicy: Parallel\n  replicas: 1\n  updateStrategy:\n    type: OnDelete\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: vault\n      app.kubernetes.io/instance: vault\n      component: server\n  template:\n    metadata:\n      labels:\n        helm.sh/chart: vault-0.6.0\n        app.kubernetes.io/name: vault\n        app.kubernetes.io/instance: vault\n        component: server\n    spec:\n      \n      \n      \n      terminationGracePeriodSeconds: 10\n      serviceAccountName: vault\n      \n      securityContext:\n        runAsNonRoot: true\n        runAsGroup: 1000\n        runAsUser: 100\n        fsGroup: 1000\n      volumes:\n        \n        - name: home\n          emptyDir: {}\n      containers:\n        - name: vault\n          \n          image: vault:1.4.2\n          imagePullPolicy: IfNotPresent\n          command: \n          args: \n          env:\n            - name: HOST_IP\n              valueFrom:\n                fieldRef:\n                  fieldPath: status.hostIP\n            - name: POD_IP\n              valueFrom:\n                fieldRef:\n                  fieldPath: status.podIP\n            - name: VAULT_K8S_POD_NAME\n              valueFrom:\n                fieldRef:\n                  fieldPath: metadata.name\n            - name: VAULT_K8S_NAMESPACE\n              valueFrom:\n                fieldRef:\n                  fieldPath: metadata.namespace\n            - name: VAULT_ADDR\n              value: \"http://127.0.0.1:8200\"\n            - name: VAULT_API_ADDR\n              value: \"http://$(POD_IP):8200\"\n            - name: SKIP_CHOWN\n              value: \"true\"\n            - name: SKIP_SETCAP\n              value: \"true\"\n            - name: HOSTNAME\n              valueFrom:\n                fieldRef:\n                  fieldPath: metadata.name\n            - name: VAULT_CLUSTER_ADDR\n              value: \"https://$(HOSTNAME).vault-internal:8201\"\n            - name: HOME\n              value: \"/home/vault\"\n            \n            - name: VAULT_DEV_ROOT_TOKEN_ID\n              value: \"root\"\n  \n            \n            \n          volumeMounts:\n          \n  \n  \n            - name: home\n              mountPath: /home/vault\n          ports:\n            - containerPort: 8200\n              name: http\n            - containerPort: 8201\n              name: https-internal\n            - containerPort: 8202\n              name: http-rep\n          readinessProbe:\n            # Check status; unsealed vault servers return 0\n            # The exit code reflects the seal status:\n            #   0 - unsealed\n            #   1 - error\n            #   2 - sealed\n            exec:\n              command: [\"/bin/sh\", \"-ec\", \"vault status -tls-skip-verify\"]\n            failureThreshold: 2\n            initialDelaySeconds: 5\n            periodSeconds: 3\n            successThreshold: 1\n            timeoutSeconds: 5\n          lifecycle:\n            # Vault container doesn't receive SIGTERM from Kubernetes\n            # and after the grace period ends, Kube sends SIGKILL.  This\n            # causes issues with graceful shutdowns such as deregistering itself\n            # from Consul (zombie services).\n            preStop:\n              exec:\n                command: [\n                  \"/bin/sh\", \"-c\",\n                  # Adding a sleep here to give the pod eviction a\n                  # chance to propagate, so requests will not be made\n                  # to this pod while it's terminating\n                  \"sleep 5 && kill -SIGTERM $(pidof vault)\",\n                ]\n"
  },
  {
    "path": "examples/pki.yml",
    "content": "apiVersion: \"koudingspawn.de/v1\"\nkind: Vault\nmetadata:\n  name: test-pki\nspec:\n  path: \"testpki/issue/testrole\"\n  type: \"PKI\"\n  pkiConfiguration:\n    commonName: \"vault.koudingspawn.de\"\n    ttl: \"7m\"\n"
  },
  {
    "path": "examples/pki_chain.yml",
    "content": "apiVersion: \"koudingspawn.de/v1\"\nkind: Vault\nmetadata:\n  name: test-pki\nspec:\n  path: \"pki_int/issue/testrole\"\n  type: \"PKI\"\n  pkiConfiguration:\n    commonName: \"localhost\"\n    ttl: \"7m\"\n"
  },
  {
    "path": "examples/pkijks.yml",
    "content": "apiVersion: \"koudingspawn.de/v1\"\nkind: Vault\nmetadata:\n  name: test-pkijks\nspec:\n  path: \"testpki/issue/testrole\"\n  type: \"PKIJKS\"\n  jksConfiguration:\n    caAlias: CARoot\n  pkiConfiguration:\n    commonName: \"vault.koudingspawn.de\"\n    ttl: \"7m\"\n"
  },
  {
    "path": "examples/properties.yml",
    "content": "apiVersion: \"koudingspawn.de/v1\"\nkind: Vault\nmetadata:\n  name: properties-example\nspec:\n  type: \"PROPERTIES\"\n  propertiesConfiguration:\n    context:\n      contextKey: value\n    files:\n      test.properties: |\n        test={{ contextKey }}\n        datasource.username={{ vault.lookupV2('keyvaluev2/database/root').get('username') }}\n        datasource.password={{ vault.lookupV2('keyvaluev2/database/root').get('password') }}\n        datasource.host={{ vault.lookup('keyvaluev1/database/host', 'host') }}\n"
  },
  {
    "path": "mvnw",
    "content": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the NOTICE file\n# distributed with this work for additional information\n# regarding copyright ownership.  The ASF licenses this file\n# to you under the Apache License, Version 2.0 (the\n# \"License\"); you may not use this file except in compliance\n# with the License.  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,\n# software distributed under the License is distributed on an\n# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n# KIND, either express or implied.  See the License for the\n# specific language governing permissions and limitations\n# under the License.\n# ----------------------------------------------------------------------------\n\n# ----------------------------------------------------------------------------\n# Maven2 Start Up Batch script\n#\n# Required ENV vars:\n# ------------------\n#   JAVA_HOME - location of a JDK home dir\n#\n# Optional ENV vars\n# -----------------\n#   M2_HOME - location of maven2's installed home dir\n#   MAVEN_OPTS - parameters passed to the Java VM when running Maven\n#     e.g. to debug Maven itself, use\n#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\n#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files\n# ----------------------------------------------------------------------------\n\nif [ -z \"$MAVEN_SKIP_RC\" ] ; then\n\n  if [ -f /etc/mavenrc ] ; then\n    . /etc/mavenrc\n  fi\n\n  if [ -f \"$HOME/.mavenrc\" ] ; then\n    . \"$HOME/.mavenrc\"\n  fi\n\nfi\n\n# OS specific support.  $var _must_ be set to either true or false.\ncygwin=false;\ndarwin=false;\nmingw=false\ncase \"`uname`\" in\n  CYGWIN*) cygwin=true ;;\n  MINGW*) mingw=true;;\n  Darwin*) darwin=true\n    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home\n    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html\n    if [ -z \"$JAVA_HOME\" ]; then\n      if [ -x \"/usr/libexec/java_home\" ]; then\n        export JAVA_HOME=\"`/usr/libexec/java_home`\"\n      else\n        export JAVA_HOME=\"/Library/Java/Home\"\n      fi\n    fi\n    ;;\nesac\n\nif [ -z \"$JAVA_HOME\" ] ; then\n  if [ -r /etc/gentoo-release ] ; then\n    JAVA_HOME=`java-config --jre-home`\n  fi\nfi\n\nif [ -z \"$M2_HOME\" ] ; then\n  ## resolve links - $0 may be a link to maven's home\n  PRG=\"$0\"\n\n  # need this for relative symlinks\n  while [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n      PRG=\"$link\"\n    else\n      PRG=\"`dirname \"$PRG\"`/$link\"\n    fi\n  done\n\n  saveddir=`pwd`\n\n  M2_HOME=`dirname \"$PRG\"`/..\n\n  # make it fully qualified\n  M2_HOME=`cd \"$M2_HOME\" && pwd`\n\n  cd \"$saveddir\"\n  # echo Using m2 at $M2_HOME\nfi\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched\nif $cygwin ; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=`cygpath --unix \"$M2_HOME\"`\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\n  [ -n \"$CLASSPATH\" ] &&\n    CLASSPATH=`cygpath --path --unix \"$CLASSPATH\"`\nfi\n\n# For Migwn, ensure paths are in UNIX format before anything is touched\nif $mingw ; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=\"`(cd \"$M2_HOME\"; pwd)`\"\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=\"`(cd \"$JAVA_HOME\"; pwd)`\"\n  # TODO classpath?\nfi\n\nif [ -z \"$JAVA_HOME\" ]; then\n  javaExecutable=\"`which javac`\"\n  if [ -n \"$javaExecutable\" ] && ! [ \"`expr \\\"$javaExecutable\\\" : '\\([^ ]*\\)'`\" = \"no\" ]; then\n    # readlink(1) is not available as standard on Solaris 10.\n    readLink=`which readlink`\n    if [ ! `expr \"$readLink\" : '\\([^ ]*\\)'` = \"no\" ]; then\n      if $darwin ; then\n        javaHome=\"`dirname \\\"$javaExecutable\\\"`\"\n        javaExecutable=\"`cd \\\"$javaHome\\\" && pwd -P`/javac\"\n      else\n        javaExecutable=\"`readlink -f \\\"$javaExecutable\\\"`\"\n      fi\n      javaHome=\"`dirname \\\"$javaExecutable\\\"`\"\n      javaHome=`expr \"$javaHome\" : '\\(.*\\)/bin'`\n      JAVA_HOME=\"$javaHome\"\n      export JAVA_HOME\n    fi\n  fi\nfi\n\nif [ -z \"$JAVACMD\" ] ; then\n  if [ -n \"$JAVA_HOME\"  ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n      # IBM's JDK on AIX uses strange locations for the executables\n      JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n      JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n  else\n    JAVACMD=\"`which java`\"\n  fi\nfi\n\nif [ ! -x \"$JAVACMD\" ] ; then\n  echo \"Error: JAVA_HOME is not defined correctly.\" >&2\n  echo \"  We cannot execute $JAVACMD\" >&2\n  exit 1\nfi\n\nif [ -z \"$JAVA_HOME\" ] ; then\n  echo \"Warning: JAVA_HOME environment variable is not set.\"\nfi\n\nCLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher\n\n# traverses directory structure from process work directory to filesystem root\n# first directory with .mvn subdirectory is considered project base directory\nfind_maven_basedir() {\n\n  if [ -z \"$1\" ]\n  then\n    echo \"Path not specified to find_maven_basedir\"\n    return 1\n  fi\n\n  basedir=\"$1\"\n  wdir=\"$1\"\n  while [ \"$wdir\" != '/' ] ; do\n    if [ -d \"$wdir\"/.mvn ] ; then\n      basedir=$wdir\n      break\n    fi\n    # workaround for JBEAP-8937 (on Solaris 10/Sparc)\n    if [ -d \"${wdir}\" ]; then\n      wdir=`cd \"$wdir/..\"; pwd`\n    fi\n    # end of workaround\n  done\n  echo \"${basedir}\"\n}\n\n# concatenates all lines of a file\nconcat_lines() {\n  if [ -f \"$1\" ]; then\n    echo \"$(tr -s '\\n' ' ' < \"$1\")\"\n  fi\n}\n\nBASE_DIR=`find_maven_basedir \"$(pwd)\"`\nif [ -z \"$BASE_DIR\" ]; then\n  exit 1;\nfi\n\nexport MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-\"$BASE_DIR\"}\necho $MAVEN_PROJECTBASEDIR\nMAVEN_OPTS=\"$(concat_lines \"$MAVEN_PROJECTBASEDIR/.mvn/jvm.config\") $MAVEN_OPTS\"\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=`cygpath --path --windows \"$M2_HOME\"`\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=`cygpath --path --windows \"$JAVA_HOME\"`\n  [ -n \"$CLASSPATH\" ] &&\n    CLASSPATH=`cygpath --path --windows \"$CLASSPATH\"`\n  [ -n \"$MAVEN_PROJECTBASEDIR\" ] &&\n    MAVEN_PROJECTBASEDIR=`cygpath --path --windows \"$MAVEN_PROJECTBASEDIR\"`\nfi\n\nWRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\n\nexec \"$JAVACMD\" \\\n  $MAVEN_OPTS \\\n  -classpath \"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar\" \\\n  \"-Dmaven.home=${M2_HOME}\" \"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}\" \\\n  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG \"$@\"\n"
  },
  {
    "path": "mvnw.cmd",
    "content": "@REM ----------------------------------------------------------------------------\n@REM Licensed to the Apache Software Foundation (ASF) under one\n@REM or more contributor license agreements.  See the NOTICE file\n@REM distributed with this work for additional information\n@REM regarding copyright ownership.  The ASF licenses this file\n@REM to you under the Apache License, Version 2.0 (the\n@REM \"License\"); you may not use this file except in compliance\n@REM with the License.  You may obtain a copy of the License at\n@REM\n@REM    http://www.apache.org/licenses/LICENSE-2.0\n@REM\n@REM Unless required by applicable law or agreed to in writing,\n@REM software distributed under the License is distributed on an\n@REM \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n@REM KIND, either express or implied.  See the License for the\n@REM specific language governing permissions and limitations\n@REM under the License.\n@REM ----------------------------------------------------------------------------\n\n@REM ----------------------------------------------------------------------------\n@REM Maven2 Start Up Batch script\n@REM\n@REM Required ENV vars:\n@REM JAVA_HOME - location of a JDK home dir\n@REM\n@REM Optional ENV vars\n@REM M2_HOME - location of maven2's installed home dir\n@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands\n@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending\n@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven\n@REM     e.g. to debug Maven itself, use\n@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\n@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files\n@REM ----------------------------------------------------------------------------\n\n@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'\n@echo off\n@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'\n@if \"%MAVEN_BATCH_ECHO%\" == \"on\"  echo %MAVEN_BATCH_ECHO%\n\n@REM set %HOME% to equivalent of $HOME\nif \"%HOME%\" == \"\" (set \"HOME=%HOMEDRIVE%%HOMEPATH%\")\n\n@REM Execute a user defined script before this one\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPre\n@REM check for pre script, once with legacy .bat ending and once with .cmd ending\nif exist \"%HOME%\\mavenrc_pre.bat\" call \"%HOME%\\mavenrc_pre.bat\"\nif exist \"%HOME%\\mavenrc_pre.cmd\" call \"%HOME%\\mavenrc_pre.cmd\"\n:skipRcPre\n\n@setlocal\n\nset ERROR_CODE=0\n\n@REM To isolate internal variables from possible post scripts, we use another setlocal\n@setlocal\n\n@REM ==== START VALIDATION ====\nif not \"%JAVA_HOME%\" == \"\" goto OkJHome\n\necho.\necho Error: JAVA_HOME not found in your environment. >&2\necho Please set the JAVA_HOME variable in your environment to match the >&2\necho location of your Java installation. >&2\necho.\ngoto error\n\n:OkJHome\nif exist \"%JAVA_HOME%\\bin\\java.exe\" goto init\n\necho.\necho Error: JAVA_HOME is set to an invalid directory. >&2\necho JAVA_HOME = \"%JAVA_HOME%\" >&2\necho Please set the JAVA_HOME variable in your environment to match the >&2\necho location of your Java installation. >&2\necho.\ngoto error\n\n@REM ==== END VALIDATION ====\n\n:init\n\n@REM Find the project base dir, i.e. the directory that contains the folder \".mvn\".\n@REM Fallback to current working directory if not found.\n\nset MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%\nIF NOT \"%MAVEN_PROJECTBASEDIR%\"==\"\" goto endDetectBaseDir\n\nset EXEC_DIR=%CD%\nset WDIR=%EXEC_DIR%\n:findBaseDir\nIF EXIST \"%WDIR%\"\\.mvn goto baseDirFound\ncd ..\nIF \"%WDIR%\"==\"%CD%\" goto baseDirNotFound\nset WDIR=%CD%\ngoto findBaseDir\n\n:baseDirFound\nset MAVEN_PROJECTBASEDIR=%WDIR%\ncd \"%EXEC_DIR%\"\ngoto endDetectBaseDir\n\n:baseDirNotFound\nset MAVEN_PROJECTBASEDIR=%EXEC_DIR%\ncd \"%EXEC_DIR%\"\n\n:endDetectBaseDir\n\nIF NOT EXIST \"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\" goto endReadAdditionalConfig\n\n@setlocal EnableExtensions EnableDelayedExpansion\nfor /F \"usebackq delims=\" %%a in (\"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a\n@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%\n\n:endReadAdditionalConfig\n\nSET MAVEN_JAVA_EXE=\"%JAVA_HOME%\\bin\\java.exe\"\n\nset WRAPPER_JAR=\"%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.jar\"\nset WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\n\n%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% \"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%\" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*\nif ERRORLEVEL 1 goto error\ngoto end\n\n:error\nset ERROR_CODE=1\n\n:end\n@endlocal & set ERROR_CODE=%ERROR_CODE%\n\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPost\n@REM check for post script, once with legacy .bat ending and once with .cmd ending\nif exist \"%HOME%\\mavenrc_post.bat\" call \"%HOME%\\mavenrc_post.bat\"\nif exist \"%HOME%\\mavenrc_post.cmd\" call \"%HOME%\\mavenrc_post.cmd\"\n:skipRcPost\n\n@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'\nif \"%MAVEN_BATCH_PAUSE%\" == \"on\" pause\n\nif \"%MAVEN_TERMINATE_CMD%\" == \"on\" exit %ERROR_CODE%\n\nexit /B %ERROR_CODE%\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\txsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<groupId>de.koudingspawn</groupId>\n\t<artifactId>vault</artifactId>\n\t<version>0.0.1-SNAPSHOT</version>\n\t<packaging>jar</packaging>\n\n\t<name>vault-crd</name>\n\t<description>Vault CRD for sharing Vault Secrets to Kubernetes</description>\n\n\t<parent>\n\t\t<groupId>org.springframework.boot</groupId>\n\t\t<artifactId>spring-boot-starter-parent</artifactId>\n\t\t<version>3.1.3</version>\n\t\t<relativePath/> <!-- lookup parent from repository -->\n\t</parent>\n\n\t<properties>\n\t\t<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n\t\t<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n\t\t<java.version>17</java.version>\n\t\t<fabric8.version>6.8.1</fabric8.version>\n\t</properties>\n\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.hibernate.validator</groupId>\n\t\t\t<artifactId>hibernate-validator</artifactId>\n\t\t\t<version>8.0.1.Final</version>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-web</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.vault</groupId>\n\t\t\t<artifactId>spring-vault-core</artifactId>\n\t\t\t<version>3.0.4</version>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-actuator</artifactId>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>io.fabric8</groupId>\n\t\t\t<artifactId>kubernetes-client</artifactId>\n\t\t\t<version>${fabric8.version}</version>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>com.github.ben-manes.caffeine</groupId>\n\t\t\t<artifactId>caffeine</artifactId>\n\t\t\t<version>3.1.8</version>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>com.hubspot.jinjava</groupId>\n\t\t\t<artifactId>jinjava</artifactId>\n\t\t\t<version>2.7.1</version>\n\t\t</dependency>\n\n\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-test</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>com.github.tomakehurst</groupId>\n\t\t\t<artifactId>wiremock</artifactId>\n\t\t\t<version>3.0.1</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.bouncycastle</groupId>\n\t\t\t<artifactId>bcpkix-jdk15on</artifactId>\n\t\t\t<version>1.70</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\n\t\t<dependency>\n\t\t\t<groupId>org.junit.vintage</groupId>\n\t\t\t<artifactId>junit-vintage-engine</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\n\t</dependencies>\n\n\t<build>\n\t\t<finalName>${project.name}</finalName>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t<artifactId>spring-boot-maven-plugin</artifactId>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t<artifactId>maven-compiler-plugin</artifactId>\n\t\t\t\t<version>3.10.1</version>\n\t\t\t\t<configuration>\n                    <release>17</release>\n\t\t\t\t\t<fork>true</fork>\n\t\t\t\t\t<compilerArgument>-XDignore.symbol.file</compilerArgument>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n\n</project>\n"
  },
  {
    "path": "readme.md",
    "content": "# What is Vault-CRD?\n\nVault-CRD is a custom resource definition for holding secrets that are stored in HashiCorp Vault up to date with\nKubernetes secrets.\n\nThe following Secret engines of Vault are supported:\n\n* KV (Version 1)\n* KV (Version 2)\n* PKI\n\nThe following types of secrets can be managed by Vault-CRD:\n\n* Docker Pull Secret (DockerCfg)\n* Ingress Certificates\n* JKS Key Stores\n\nFor more details please see: [https://vault.koudingspawn.de/how-does-vault-crd-work](https://vault.koudingspawn.de/how-does-vault-crd-work)\n\n## Note\n\nDue to Docker's decision to discontinue its Free Teams, I decided to host my Docker images on GHCR (GitHub Container\nRegistry) and public ECR (Elastic Container Registry) in the future. "
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/Constants.java",
    "content": "package de.koudingspawn.vault;\n\npublic class Constants {\n\n    private Constants() {\n    }\n\n    public static final String DATE_FORMAT = \"yyyy-MM-dd'T'HH:mm'Z'\";\n    public static final String COMPARE_ANNOTATION = \"/compare\";\n    public static final String LAST_UPDATE_ANNOTATION = \"/lastUpdated\";\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/VaultApplication.java",
    "content": "package de.koudingspawn.vault;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.scheduling.annotation.EnableScheduling;\n\n@SpringBootApplication\n@EnableScheduling\npublic class VaultApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(VaultApplication.class, args);\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/admissionreview/AdmissionReviewRestService.java",
    "content": "package de.koudingspawn.vault.admissionreview;\n\nimport io.fabric8.kubernetes.api.model.admission.v1.AdmissionResponse;\nimport io.fabric8.kubernetes.api.model.admission.v1.AdmissionReview;\nimport io.fabric8.kubernetes.api.model.admission.v1.AdmissionReviewBuilder;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n@RestController\n@RequestMapping(\"/validation/vault-crd\")\npublic class AdmissionReviewRestService {\n\n    private final AdmissionReviewService admissionReviewService;\n\n    public AdmissionReviewRestService(AdmissionReviewService admissionReviewService) {\n        this.admissionReviewService = admissionReviewService;\n    }\n\n    @PostMapping\n    public AdmissionReview validate(@RequestBody AdmissionReview admissionRequest) {\n        AdmissionResponse admissionResponse = admissionReviewService.validate(admissionRequest.getRequest());\n        return new AdmissionReviewBuilder()\n                .withResponse(admissionResponse)\n                .build();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/admissionreview/AdmissionReviewService.java",
    "content": "package de.koudingspawn.vault.admissionreview;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.vault.VaultService;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport io.fabric8.kubernetes.api.model.Status;\nimport io.fabric8.kubernetes.api.model.StatusBuilder;\nimport io.fabric8.kubernetes.api.model.admission.v1.AdmissionRequest;\nimport io.fabric8.kubernetes.api.model.admission.v1.AdmissionResponse;\nimport io.fabric8.kubernetes.api.model.admission.v1.AdmissionResponseBuilder;\nimport io.fabric8.kubernetes.client.utils.Serialization;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Service;\n\n@Service\npublic class AdmissionReviewService {\n\n    private static final Logger log = LoggerFactory.getLogger(AdmissionReviewService.class);\n\n    private final VaultService vaultService;\n\n    public AdmissionReviewService(VaultService vaultService) {\n        this.vaultService = vaultService;\n    }\n\n    public AdmissionResponse validate(AdmissionRequest admissionRequest) {\n        try {\n            String s = Serialization.asYaml(admissionRequest.getObject());\n            Vault vault = Serialization.unmarshal(s, Vault.class);\n            vaultService.generateSecret(vault);\n        } catch (ClassCastException ex) {\n            log.error(\"Received Admission Request of invalid type!\");\n            return invalidRequest(admissionRequest.getUid(), \"Received Admission Request of invalid type!\");\n        } catch (SecretNotAccessibleException e) {\n            log.error(\"Admission Request failed with Secret not Accessible Exception\", e);\n            return invalidRequest(admissionRequest.getUid(), e.getMessage());\n        }\n\n        return validRequest(admissionRequest.getUid());\n    }\n\n    private AdmissionResponse validRequest(String uuid) {\n        return new AdmissionResponseBuilder()\n                .withAllowed(true)\n                .withUid(uuid)\n                .build();\n    }\n\n    private AdmissionResponse invalidRequest(String uid, String message) {\n        Status status = new StatusBuilder()\n                .withCode(400)\n                .withMessage(message)\n                .build();\n        return new AdmissionResponseBuilder()\n                .withAllowed(false)\n                .withUid(uid)\n                .withStatus(status)\n                .build();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/crd/Vault.java",
    "content": "package de.koudingspawn.vault.crd;\n\nimport io.fabric8.kubernetes.api.model.Namespaced;\nimport io.fabric8.kubernetes.client.CustomResource;\nimport io.fabric8.kubernetes.model.annotation.*;\n\nimport java.util.HashMap;\nimport java.util.Objects;\n\n@Version(Vault.VERSION)\n@Group(Vault.GROUP)\n@Kind(Vault.KIND)\n@Singular(Vault.SINGULAR)\n@Plural(Vault.PLURAL)\npublic class Vault extends CustomResource<VaultSpec, Void> implements Namespaced {\n\n    public static final String GROUP = \"koudingspawn.de\";\n    public static final String VERSION = \"v1\";\n    public static final String KIND = \"Vault\";\n    public static final String SINGULAR = \"vault\";\n    public static final String PLURAL = \"vault\";\n\n    public boolean modifyHandlerEquals(Object o) {\n        if (o == null || getClass() != o.getClass()) return false;\n        Vault vault = (Vault) o;\n\n        // spec equals\n        if (vault.getSpec() == null && spec != null) return false;\n        if (vault.getSpec() != null && spec == null) return false;\n        if (vault.getSpec() != null && spec != null) {\n            if (!vault.getSpec().equals(spec)) return false;\n        } // null && null => true for spec\n\n        // metadata equals\n        if (vault.getMetadata() == null && getMetadata() == null) return true;\n        if (vault.getMetadata() == null) return false;\n        if (getMetadata() == null) return false;\n\n        // metadata.name, metadata.namespace, metadata.uid equals\n        if (!vault.getMetadata().getName().equals(getMetadata().getName())) return false;\n        if (!vault.getMetadata().getNamespace().equals(getMetadata().getNamespace())) return false;\n        if (!vault.getMetadata().getUid().equals(getMetadata().getUid())) return false;\n\n        // metadata.labels equals\n        if (vault.getMetadata().getLabels() == null && getMetadata().getLabels() != null) return false;\n        if (vault.getMetadata().getLabels() != null && getMetadata().getLabels() == null) return false;\n        if (!Objects.equals(vault.getMetadata().getLabels(), getMetadata().getLabels())) return false;\n\n        // metadata.annotations equals\n        if (vault.getMetadata().getAnnotations() == null && getMetadata().getAnnotations() != null) return false;\n        if (vault.getMetadata().getAnnotations() != null && getMetadata().getAnnotations() == null) return false;\n        if (vault.getMetadata().getAnnotations() != null && getMetadata().getAnnotations() != null) {\n            HashMap<String, String> vaultAnnotations = new HashMap<>(vault.getMetadata().getAnnotations());\n            vaultAnnotations.remove(\"kubectl.kubernetes.io/last-applied-configuration\");\n\n            HashMap<String, String> annotations = new HashMap<>(getMetadata().getAnnotations());\n            annotations.remove(\"kubectl.kubernetes.io/last-applied-configuration\");\n            return Objects.equals(vaultAnnotations, annotations);\n        }\n\n\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/crd/VaultChangeAdjustmentCallback.java",
    "content": "package de.koudingspawn.vault.crd;\n\nimport java.util.Objects;\n\npublic class VaultChangeAdjustmentCallback {\n\n    private String type;\n    private String name;\n\n    public VaultChangeAdjustmentCallback() {\n    }\n\n    public String getType() {\n        return type;\n    }\n\n    public void setType(String type) {\n        this.type = type;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    @Override\n    public String toString() {\n        return type + \"/\" + name;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        VaultChangeAdjustmentCallback that = (VaultChangeAdjustmentCallback) o;\n        return Objects.equals(type, that.type) && Objects.equals(name, that.name);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(type, name);\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/crd/VaultDockerCfgConfiguration.java",
    "content": "package de.koudingspawn.vault.crd;\n\nimport java.util.Objects;\n\npublic class VaultDockerCfgConfiguration {\n\n    private VaultType type;\n    private Integer version;\n\n    public VaultDockerCfgConfiguration() {\n        this.type = VaultType.KEYVALUE;\n    }\n\n\n    public VaultType getType() {\n        return type;\n    }\n\n    public void setType(VaultType version) {\n        this.type = version;\n    }\n\n    public Integer getVersion() {\n        return version;\n    }\n\n    public void setVersion(Integer version) {\n        this.version = version;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        VaultDockerCfgConfiguration that = (VaultDockerCfgConfiguration) o;\n        return type == that.type && Objects.equals(version, that.version);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(type, version);\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/crd/VaultJKSConfiguration.java",
    "content": "package de.koudingspawn.vault.crd;\n\nimport java.util.Objects;\n\npublic class VaultJKSConfiguration {\n\n    private String password;\n    private String alias;\n    private String keyName;\n    private String caAlias;\n\n    public String getPassword() {\n        return password;\n    }\n\n    public void setPassword(String password) {\n        this.password = password;\n    }\n\n    public String getAlias() {\n        return alias;\n    }\n\n    public void setAlias(String alias) {\n        this.alias = alias;\n    }\n\n    public String getKeyName() {\n        return keyName;\n    }\n\n    public void setKeyName(String keyName) {\n        this.keyName = keyName;\n    }\n\n    public String getCaAlias() {\n        return caAlias;\n    }\n\n    public void setCaAlias(String caAlias) {\n        this.caAlias = caAlias;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        VaultJKSConfiguration that = (VaultJKSConfiguration) o;\n        return Objects.equals(password, that.password) && Objects.equals(alias, that.alias) && Objects.equals(keyName, that.keyName) && Objects.equals(caAlias, that.caAlias);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(password, alias, keyName, caAlias);\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/crd/VaultList.java",
    "content": "package de.koudingspawn.vault.crd;\n\nimport io.fabric8.kubernetes.api.model.DefaultKubernetesResourceList;\n\npublic class VaultList extends DefaultKubernetesResourceList<Vault> {\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/crd/VaultPkiConfiguration.java",
    "content": "package de.koudingspawn.vault.crd;\n\nimport java.util.Objects;\n\npublic class VaultPkiConfiguration {\n\n    private String commonName;\n    private String altNames;\n    private String ipSans;\n    private String ttl;\n\n    public String getCommonName() {\n        return commonName;\n    }\n\n    public void setCommonName(String commonName) {\n        this.commonName = commonName;\n    }\n\n    public String getAltNames() {\n        return altNames;\n    }\n\n    public void setAltNames(String altNames) {\n        this.altNames = altNames;\n    }\n\n    public String getIpSans() {\n        return ipSans;\n    }\n\n    public void setIpSans(String ipSans) {\n        this.ipSans = ipSans;\n    }\n\n    public String getTtl() {\n        return ttl;\n    }\n\n    public void setTtl(String ttl) {\n        this.ttl = ttl;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        VaultPkiConfiguration that = (VaultPkiConfiguration) o;\n        return Objects.equals(commonName, that.commonName) && Objects.equals(altNames, that.altNames) && Objects.equals(ipSans, that.ipSans) && Objects.equals(ttl, that.ttl);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(commonName, altNames, ipSans, ttl);\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/crd/VaultPropertiesConfiguration.java",
    "content": "package de.koudingspawn.vault.crd;\n\nimport java.util.HashMap;\nimport java.util.Objects;\n\npublic class VaultPropertiesConfiguration {\n\n    private HashMap<String, String> files;\n    private HashMap<String, String> context;\n\n    public HashMap<String, String> getFiles() {\n        return files;\n    }\n\n    public void setFiles(HashMap<String, String> files) {\n        this.files = files;\n    }\n\n    public HashMap<String, String> getContext() {\n        return context;\n    }\n\n    public void setContext(HashMap<String, String> context) {\n        this.context = context;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        VaultPropertiesConfiguration that = (VaultPropertiesConfiguration) o;\n        return Objects.equals(files, that.files) && Objects.equals(context, that.context);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(files, context);\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/crd/VaultSpec.java",
    "content": "package de.koudingspawn.vault.crd;\n\nimport com.fasterxml.jackson.annotation.JsonInclude;\nimport com.fasterxml.jackson.databind.annotation.JsonDeserialize;\nimport io.fabric8.kubernetes.api.model.KubernetesResource;\n\nimport java.util.Objects;\n\n@JsonDeserialize\n@JsonInclude(JsonInclude.Include.NON_NULL)\npublic class VaultSpec implements KubernetesResource {\n    private String path;\n    private VaultType type;\n    private VaultPkiConfiguration pkiConfiguration;\n    private VaultJKSConfiguration jksConfiguration;\n    private VaultVersionedConfiguration versionConfiguration;\n    private VaultPropertiesConfiguration propertiesConfiguration;\n    private VaultDockerCfgConfiguration dockerCfgConfiguration;\n    private VaultChangeAdjustmentCallback changeAdjustmentCallback;\n\n    public String getPath() {\n        return path;\n    }\n\n    public void setPath(String path) {\n        this.path = path;\n    }\n\n    public VaultType getType() {\n        return type;\n    }\n\n    public void setType(VaultType type) {\n        this.type = type;\n    }\n\n    public VaultPkiConfiguration getPkiConfiguration() {\n        return pkiConfiguration;\n    }\n\n    public void setPkiConfiguration(VaultPkiConfiguration pkiConfiguration) {\n        this.pkiConfiguration = pkiConfiguration;\n    }\n\n    public VaultJKSConfiguration getJksConfiguration() {\n        return jksConfiguration;\n    }\n\n    public void setJksConfiguration(VaultJKSConfiguration jksConfiguration) {\n        this.jksConfiguration = jksConfiguration;\n    }\n\n    public VaultVersionedConfiguration getVersionConfiguration() {\n        return versionConfiguration;\n    }\n\n    public void setVersionConfiguration(VaultVersionedConfiguration versionConfiguration) {\n        this.versionConfiguration = versionConfiguration;\n    }\n\n    public VaultPropertiesConfiguration getPropertiesConfiguration() {\n        return propertiesConfiguration;\n    }\n\n    public void setPropertiesConfiguration(VaultPropertiesConfiguration propertiesConfiguration) {\n        this.propertiesConfiguration = propertiesConfiguration;\n    }\n\n    public VaultDockerCfgConfiguration getDockerCfgConfiguration() {\n        return dockerCfgConfiguration;\n    }\n\n    public void setDockerCfgConfiguration(VaultDockerCfgConfiguration dockerCfgConfiguration) {\n        this.dockerCfgConfiguration = dockerCfgConfiguration;\n    }\n\n    public VaultChangeAdjustmentCallback getChangeAdjustmentCallback() {\n        return changeAdjustmentCallback;\n    }\n\n    public void setChangeAdjustmentCallback(VaultChangeAdjustmentCallback changeAdjustmentCallback) {\n        this.changeAdjustmentCallback = changeAdjustmentCallback;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        VaultSpec vaultSpec = (VaultSpec) o;\n        return Objects.equals(path, vaultSpec.path) && type == vaultSpec.type && Objects.equals(pkiConfiguration, vaultSpec.pkiConfiguration) && Objects.equals(jksConfiguration, vaultSpec.jksConfiguration) && Objects.equals(versionConfiguration, vaultSpec.versionConfiguration) && Objects.equals(propertiesConfiguration, vaultSpec.propertiesConfiguration) && Objects.equals(dockerCfgConfiguration, vaultSpec.dockerCfgConfiguration) && Objects.equals(changeAdjustmentCallback, vaultSpec.changeAdjustmentCallback);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(path, type, pkiConfiguration, jksConfiguration, versionConfiguration, propertiesConfiguration, dockerCfgConfiguration, changeAdjustmentCallback);\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/crd/VaultType.java",
    "content": "package de.koudingspawn.vault.crd;\n\npublic enum VaultType {\n    PKI, CERT, DOCKERCFG, KEYVALUE, PKIJKS, CERTJKS, KEYVALUEV2, PROPERTIES\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/crd/VaultVersionedConfiguration.java",
    "content": "package de.koudingspawn.vault.crd;\n\nimport java.util.Objects;\n\npublic class VaultVersionedConfiguration {\n\n    private Integer version;\n\n    public Integer getVersion() {\n        return version;\n    }\n\n    public void setVersion(Integer version) {\n        this.version = version;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        VaultVersionedConfiguration that = (VaultVersionedConfiguration) o;\n        return Objects.equals(version, that.version);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(version);\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/kubernetes/ChangeAdjustmentService.java",
    "content": "package de.koudingspawn.vault.kubernetes;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.crd.VaultChangeAdjustmentCallback;\nimport io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;\nimport io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Service;\n\n@Service\npublic class ChangeAdjustmentService {\n\n    private static final Logger log = LoggerFactory.getLogger(ChangeAdjustmentService.class);\n\n    private final KubernetesClient client;\n\n    public ChangeAdjustmentService(KubernetesClient client) {\n        this.client = client;\n    }\n\n    public void handle(Vault resource) {\n        VaultChangeAdjustmentCallback changeAdjustmentCallback = resource.getSpec().getChangeAdjustmentCallback();\n        if (changeAdjustmentCallback != null && changeAdjustmentCallback.getType() != null && changeAdjustmentCallback.getName() != null) {\n            switch (changeAdjustmentCallback.getType().toLowerCase()) {\n                case \"deployment\" ->\n                        rotateDeployment(resource.getMetadata().getNamespace(), changeAdjustmentCallback.getName());\n                case \"statefulset\" ->\n                        rotateStatefulSet(resource.getMetadata().getNamespace(), changeAdjustmentCallback.getName());\n                default ->\n                        log.info(\"Currently a change adjustment is only supported for type deployment. Resource {} in namespace {} has type {}\",\n                                resource.getMetadata().getName(), resource.getMetadata().getNamespace(), changeAdjustmentCallback.getType());\n            }\n        } else {\n            log.warn(\"Change adjustment callback for resource {} in namespace {} is invalid!\", resource.getMetadata().getName(), resource.getMetadata().getNamespace());\n        }\n    }\n\n    private void rotateDeployment(String namespace, String name) {\n        try {\n            log.info(\"Start rotation of deployment {} in namespace {}\", name, namespace);\n            client.apps()\n                    .deployments()\n                    .inNamespace(namespace)\n                    .withName(name)\n                    .edit(d -> new DeploymentBuilder(d)\n                            .editSpec()\n                            .editTemplate()\n                            .editMetadata()\n                            .addToAnnotations(\"certificate-change-on\", \"vault-crd_\" + System.currentTimeMillis())\n                            .endMetadata()\n                            .endTemplate()\n                            .endSpec()\n                            .build());\n        } catch (Exception ex) {\n            log.error(\"Failed to rotate deployment {} in namespace {} with exception:\", name, namespace, ex);\n        }\n    }\n\n    private void rotateStatefulSet(String namespace, String name) {\n        try {\n            log.info(\"Start rotation of statefulSet {} in namespace {}\", name, namespace);\n            client.apps()\n                    .statefulSets()\n                    .inNamespace(namespace)\n                    .withName(name)\n                    .edit(statefulSet -> new StatefulSetBuilder(statefulSet)\n                            .editSpec()\n                            .editTemplate()\n                            .editMetadata()\n                            .addToAnnotations(\"certificate-change-on\", \"vault-crd_\" + System.currentTimeMillis())\n                            .endMetadata()\n                            .endTemplate()\n                            .endSpec()\n                            .build());\n        } catch (Exception ex) {\n            log.error(\"Failed to rotate statefulSet {} in namespace {} with exception:\", name, namespace, ex);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/kubernetes/EventHandler.java",
    "content": "package de.koudingspawn.vault.kubernetes;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.kubernetes.event.EventNotification;\nimport de.koudingspawn.vault.vault.VaultSecret;\nimport de.koudingspawn.vault.vault.VaultService;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Component;\n\nimport static de.koudingspawn.vault.kubernetes.event.EventType.*;\n\n@Component\npublic class EventHandler {\n\n    private static final Logger log = LoggerFactory.getLogger(EventHandler.class);\n\n    private final VaultService vaultService;\n    private final KubernetesService kubernetesService;\n    private final ChangeAdjustmentService changeAdjustmentService;\n    private final EventNotification eventNotification;\n    private final boolean fixOwnerReferenceEnabled;\n\n    public EventHandler(VaultService vaultService,\n                        KubernetesService kubernetesService,\n                        ChangeAdjustmentService changeAdjustmentService,\n                        EventNotification eventNotification,\n                        @Value(\"${kubernetes.ownerreference-fix.enabled:true}\") boolean fixOwnerReferenceEnabled) {\n        this.vaultService = vaultService;\n        this.kubernetesService = kubernetesService;\n        this.changeAdjustmentService = changeAdjustmentService;\n        this.eventNotification = eventNotification;\n        this.fixOwnerReferenceEnabled = fixOwnerReferenceEnabled;\n    }\n\n    public void addHandler(Vault resource) {\n        if (!kubernetesService.exists(resource)) {\n            try {\n                VaultSecret secretContent = vaultService.generateSecret(resource);\n                kubernetesService.createSecret(resource, secretContent);\n\n                eventNotification.storeNewEvent(CREATION_SUCCESSFUL, \"Successfully created secret\", resource);\n            } catch (Exception e) {\n                log.error(\"Failed to generate secret for vault resource {} in namespace {} failed with exception:\",\n                        resource.getMetadata().getName(), resource.getMetadata().getNamespace(), e);\n\n                eventNotification.storeNewEvent(CREATION_FAILED, \"Failed to generate secret with exception \" + e.getMessage(), resource);\n            }\n        } else if (fixOwnerReferenceEnabled && kubernetesService.hasBrokenOwnerReference(resource)) {\n            log.info(\"Fix owner reference for secret {} in namespace {}\", resource.getMetadata().getName(), resource.getMetadata().getNamespace());\n            modifyHandler(resource);\n\n            eventNotification.storeNewEvent(FIXED_REFERENCE, \"Fixed owner reference\", resource);\n        }\n    }\n\n    void deleteHandler(Vault resource) {\n        kubernetesService.deleteSecret(resource.getMetadata());\n\n        eventNotification.storeNewEvent(DELETION, \"Deleted secret for resource\", resource);\n    }\n\n    public void modifyHandler(Vault resource) {\n\n        try {\n            VaultSecret secretContent = vaultService.generateSecret(resource);\n            kubernetesService.modifySecret(resource, secretContent);\n\n            eventNotification.storeNewEvent(MODIFICATION_SUCCESSFUL, \"Successfully modified secret\", resource);\n\n            if (resource.getSpec().getChangeAdjustmentCallback() != null) {\n                changeAdjustmentService.handle(resource);\n                eventNotification.storeNewEvent(ROTATION,\n                        \"Successfully started rotation of associated resource \" + resource.getSpec().getChangeAdjustmentCallback().toString(), resource);\n            }\n        } catch (Exception e) {\n            log.error(\"Failed to modify secret for vault resource {} in namespace {} failed with exception:\",\n                    resource.getMetadata().getName(), resource.getMetadata().getNamespace(), e);\n\n            eventNotification.storeNewEvent(MODIFICATION_FAILED, \"Modification failed \" + e.getMessage(), resource);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/kubernetes/KubernetesConnection.java",
    "content": "package de.koudingspawn.vault.kubernetes;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.crd.VaultList;\nimport io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition;\nimport io.fabric8.kubernetes.client.Config;\nimport io.fabric8.kubernetes.client.ConfigBuilder;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.KubernetesClientBuilder;\nimport io.fabric8.kubernetes.client.dsl.MixedOperation;\nimport io.fabric8.kubernetes.client.dsl.Resource;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Profile;\n\n@Configuration\npublic class KubernetesConnection {\n\n    private static final Logger log = LoggerFactory.getLogger(KubernetesConnection.class);\n\n    @Bean\n    @Profile(\"development\")\n    public KubernetesClient testClient() {\n        Config config = new ConfigBuilder().withMasterUrl(\"http://localhost:8001\").withWatchReconnectLimit(5).build();\n        return new KubernetesClientBuilder()\n                .withConfig(config)\n                .build();\n    }\n\n    @Bean\n    @Profile(\"!development\")\n    public KubernetesClient client() {\n        return new KubernetesClientBuilder().build();\n    }\n\n    @Bean\n    public MixedOperation<Vault, VaultList, Resource<Vault>> customResource(\n            KubernetesClient client,\n            @Value(\"${kubernetes.crd.name}\") String crdName) {\n        Resource<CustomResourceDefinition> crdResource = client.apiextensions().v1().customResourceDefinitions().withName(crdName);\n        CustomResourceDefinition customResourceDefinition = crdResource.get();\n        if (customResourceDefinition == null) {\n            log.error(\"Please first apply custom resource definition and then restart vault-crd\");\n            System.exit(1);\n        }\n\n        return client.resources(Vault.class, VaultList.class);\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/kubernetes/KubernetesService.java",
    "content": "package de.koudingspawn.vault.kubernetes;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.kubernetes.cache.SecretCache;\nimport de.koudingspawn.vault.vault.VaultSecret;\nimport io.fabric8.kubernetes.api.model.DeletionPropagation;\nimport io.fabric8.kubernetes.api.model.ObjectMeta;\nimport io.fabric8.kubernetes.api.model.OwnerReference;\nimport io.fabric8.kubernetes.api.model.Secret;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.dsl.Resource;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Component;\n\nimport java.time.LocalDateTime;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\n\nimport static de.koudingspawn.vault.Constants.COMPARE_ANNOTATION;\nimport static de.koudingspawn.vault.Constants.LAST_UPDATE_ANNOTATION;\n\n@Component\npublic class KubernetesService {\n\n    private static final Logger log = LoggerFactory.getLogger(KubernetesService.class);\n\n    private final KubernetesClient client;\n    private final String crdName;\n    private final String crdGroup;\n    private final SecretCache secretCache;\n\n    public KubernetesService(KubernetesClient client,\n                             SecretCache secretCache,\n                             @Value(\"${kubernetes.crd.name}\") String crdName,\n                             @Value(\"${kubernetes.crd.group}\") String crdGroup) {\n        this.client = client;\n        this.crdName = crdName;\n        this.crdGroup = crdGroup;\n        this.secretCache = secretCache;\n    }\n\n    boolean exists(Vault resource) {\n        return getSecretByVault(resource) != null;\n    }\n\n    private Secret newSecretInstance(Vault resource, VaultSecret vaultSecret) {\n        Secret secret = new Secret();\n        secret.setType(vaultSecret.getType());\n        secret.setMetadata(metaData(resource.getMetadata(), vaultSecret.getCompare()));\n        secret.setData(vaultSecret.getData());\n\n        return secret;\n    }\n\n    void createSecret(Vault resource, VaultSecret vaultSecret) {\n        Secret secret = newSecretInstance(resource, vaultSecret);\n\n        secretCache.invalidate(secret.getMetadata().getNamespace(), secret.getMetadata().getName());\n        client.secrets().inNamespace(resource.getMetadata().getNamespace()).resource(secret).create();\n\n        log.info(\"Created secret for vault resource {} in namespace {}\", secret.getMetadata().getName(), secret.getMetadata().getNamespace());\n    }\n\n    void deleteSecret(ObjectMeta resourceMetadata) {\n        secretCache.invalidate(resourceMetadata.getNamespace(), resourceMetadata.getName());\n        client.secrets().inNamespace(resourceMetadata.getNamespace()).withName(resourceMetadata.getName()).withPropagationPolicy(DeletionPropagation.BACKGROUND).delete();\n\n        log.info(\"Deleted secret {} in namespace {}\", resourceMetadata.getName(), resourceMetadata.getNamespace());\n    }\n\n    void modifySecret(Vault resource, VaultSecret vaultSecret) {\n        Resource<Secret> secretResource = client.secrets().inNamespace(resource.getMetadata().getNamespace()).withName(resource.getMetadata().getName());\n        Secret secret;\n\n        if (secretResource.get() != null) {\n            secret = secretResource.get();\n        } else {\n            secret = newSecretInstance(resource, vaultSecret);\n        }\n\n        secret.setType(vaultSecret.getType());\n        secret.setMetadata(metaData(resource.getMetadata(), vaultSecret.getCompare()));\n        secret.setData(vaultSecret.getData());\n\n        secretCache.invalidate(resource.getMetadata().getNamespace(), resource.getMetadata().getName());\n        client.secrets().inNamespace(resource.getMetadata().getNamespace()).resource(secret).createOrReplace();\n\n        log.info(\"Modified secret {} in namespace {}\", resource.getMetadata().getName(), resource.getMetadata().getNamespace());\n    }\n\n    public Secret getSecretByVault(Vault resource) {\n        return secretCache.get(resource.getMetadata().getNamespace(), resource.getMetadata().getName());\n    }\n\n    private ObjectMeta metaData(ObjectMeta resource, String compare) {\n        ObjectMeta meta = new ObjectMeta();\n        meta.setNamespace(resource.getNamespace());\n        meta.setName(resource.getName());\n        if (resource.getLabels() != null) {\n            meta.setLabels(resource.getLabels());\n        }\n\n        if (meta.getLabels() == null) {\n            meta.setLabels(new HashMap<>());\n        }\n        meta.getLabels().put(crdName, \"vault\");\n\n        HashMap<String, String> annotations = new HashMap<>();\n        if (resource.getAnnotations() != null) {\n            annotations.putAll(resource.getAnnotations());\n        }\n        annotations.put(crdName + LAST_UPDATE_ANNOTATION, LocalDateTime.now().toString());\n        annotations.put(crdName + COMPARE_ANNOTATION, compare);\n        meta.setAnnotations(annotations);\n        meta.setOwnerReferences(getOwnerReference(resource));\n\n        return meta;\n    }\n\n    private List<OwnerReference> getOwnerReference(ObjectMeta resource) {\n        boolean blockOwnerDeletion = false;\n        boolean controller = true;\n        OwnerReference owner = new OwnerReference(\n                crdGroup + \"/v1\",\n                blockOwnerDeletion,\n                controller,\n                \"Vault\",\n                resource.getName(),\n                resource.getUid()\n        );\n        ArrayList<OwnerReference> owners = new ArrayList<>();\n        owners.add(owner);\n\n        return owners;\n    }\n\n    public boolean hasBrokenOwnerReference(Vault resource) {\n        Resource<Secret> secretResource = client.secrets().inNamespace(resource.getMetadata().getNamespace()).withName(resource.getMetadata().getName());\n\n        if (secretResource.get() != null) {\n            Secret secret = secretResource.get();\n\n            if (secret.getMetadata() != null && secret.getMetadata().getOwnerReferences() != null && secret.getMetadata().getOwnerReferences().size() == 1) {\n                OwnerReference ownerReference = secret.getMetadata().getOwnerReferences().get(0);\n                return ownerReference.getApiVersion().equals(crdName + \"/v1\");\n            }\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/kubernetes/Watcher.java",
    "content": "package de.koudingspawn.vault.kubernetes;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.kubernetes.scheduler.ScheduledRefresh;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.informers.ResourceEventHandler;\nimport io.fabric8.kubernetes.client.informers.SharedIndexInformer;\nimport io.fabric8.kubernetes.client.informers.SharedInformerFactory;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.boot.CommandLineRunner;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Profile;\n\nimport java.util.concurrent.TimeUnit;\n\n@Configuration\n@Profile(\"!test\")\npublic class Watcher {\n\n    private static final Logger log = LoggerFactory.getLogger(Watcher.class);\n\n    private final EventHandler eventHandler;\n    private final KubernetesClient client;\n    private final ScheduledRefresh scheduledRefresh;\n    private final long resyncIntervalSecond;\n\n    public Watcher(EventHandler eventHandler, KubernetesClient client, ScheduledRefresh scheduledRefresh,\n                   @Value(\"${kubernetes.interval}\") long resyncIntervalSecond) {\n        this.eventHandler = eventHandler;\n        this.client = client;\n        this.scheduledRefresh = scheduledRefresh;\n        this.resyncIntervalSecond = resyncIntervalSecond;\n    }\n\n    @Bean\n    CommandLineRunner watchForResource() {\n        return (args) -> run();\n    }\n\n    private void run() {\n        SharedInformerFactory sharedInformerFactory = client.informers();\n        SharedIndexInformer<Vault> vaultInformer = sharedInformerFactory.sharedIndexInformerFor(Vault.class, TimeUnit.SECONDS.toMillis(resyncIntervalSecond));\n        vaultInformer.addEventHandler(\n                new ResourceEventHandler<>() {\n                    @Override\n                    public void onAdd(Vault resource) {\n                        log.info(\"Received add for {} in namespace {}\", resource.getMetadata().getName(), resource.getMetadata().getNamespace());\n                        eventHandler.addHandler(resource);\n                    }\n\n                    @Override\n                    public void onUpdate(Vault oldObj, Vault resource) {\n                        if (oldObj.modifyHandlerEquals(resource)) {\n                            log.info(\"Received scheduled refresh for {} in namespace {}\", resource.getMetadata().getName(), resource.getMetadata().getNamespace());\n                            scheduledRefresh.refreshVaultResource(resource);\n                        } else {\n                            log.info(\"Received update for {} in namespace {}\", resource.getMetadata().getName(), resource.getMetadata().getNamespace());\n                            eventHandler.modifyHandler(resource);\n                        }\n                    }\n\n                    @Override\n                    public void onDelete(Vault resource, boolean deletedFinalStateUnknown) {\n                        log.info(\"Received delete for {} in namespace {}\", resource.getMetadata().getName(), resource.getMetadata().getNamespace());\n                        eventHandler.deleteHandler(resource);\n                    }\n                }\n        );\n\n        sharedInformerFactory.addSharedInformerEventListener(ex ->\n                log.error(\"Exception occurred in shared informer, but caught: {}\", ex.getMessage()));\n\n        log.info(\"Starting informer\");\n        sharedInformerFactory.startAllRegisteredInformers();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/kubernetes/cache/SecretCache.java",
    "content": "package de.koudingspawn.vault.kubernetes.cache;\n\nimport com.github.benmanes.caffeine.cache.Cache;\nimport com.github.benmanes.caffeine.cache.Caffeine;\nimport io.fabric8.kubernetes.api.model.Secret;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.informers.ResourceEventHandler;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.concurrent.TimeUnit;\n\npublic class SecretCache {\n\n    private static final Logger log = LoggerFactory.getLogger(SecretCache.class);\n\n    private final Cache<String, Secret> secretResourceCache = Caffeine.newBuilder().build();\n    private final KubernetesClient client;\n\n    public SecretCache(KubernetesClient client, boolean watch) {\n        this.client = client;\n\n        if (watch) {\n            this.watcher();\n        }\n    }\n\n    public void watcher() {\n        client.secrets().inAnyNamespace().withLabel(\"vault.koudingspawn.de=vault\").inform(\n                new ResourceEventHandler<>() {\n\n                    private String cacheKey(String namespace, String name) {\n                        return \"%s/%s\".formatted(namespace, name);\n                    }\n\n                    @Override\n                    public void onAdd(Secret obj) {\n                        String key = cacheKey(obj.getMetadata().getNamespace(), obj.getMetadata().getName());\n                        log.debug(\"Received create secret for {}\", key);\n                        secretResourceCache.put(key, obj);\n                    }\n\n                    @Override\n                    public void onUpdate(Secret oldObj, Secret newObj) {\n                        String key = cacheKey(newObj.getMetadata().getNamespace(), newObj.getMetadata().getName());\n                        log.debug(\"Received update for secret {}\", key);\n                        secretResourceCache.put(key, newObj);\n                    }\n\n                    @Override\n                    public void onDelete(Secret obj, boolean deletedFinalStateUnknown) {\n                        String key = cacheKey(obj.getMetadata().getNamespace(), obj.getMetadata().getName());\n                        log.debug(\"Invalidate secret cache for {} after delete\", key);\n                        secretResourceCache.invalidate(key);\n                    }\n                }, TimeUnit.MINUTES.toMillis(60));\n    }\n\n    public Secret get(String namespace, String name) {\n        String key = String.format(\"%s/%s\", namespace, name);\n\n        Secret cacheSecret = secretResourceCache.getIfPresent(key);\n        if (cacheSecret != null) {\n            return cacheSecret;\n        }\n\n        Secret clusterSecret = client.secrets().inNamespace(namespace).withName(name).get();\n        if (clusterSecret != null) {\n            secretResourceCache.put(key, clusterSecret);\n        }\n\n        return clusterSecret;\n    }\n\n    public void invalidate(String namespace, String name) {\n        String key = String.format(\"%s/%s\", namespace, name);\n        log.debug(\"Invalidate secret cache for {}\", key);\n        secretResourceCache.invalidate(key);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/kubernetes/cache/SecretCacheConfiguration.java",
    "content": "package de.koudingspawn.vault.kubernetes.cache;\n\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Profile;\n\n@Configuration\npublic class SecretCacheConfiguration {\n\n    @Bean\n    @Profile(\"!test\")\n    public SecretCache secretCache(KubernetesClient client) {\n        return new SecretCache(client, true);\n    }\n\n    @Bean\n    @Profile(\"test\")\n    public SecretCache testSecretCache(KubernetesClient client) {\n        return new SecretCache(client, false);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/kubernetes/event/EventNotification.java",
    "content": "package de.koudingspawn.vault.kubernetes.event;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport io.fabric8.kubernetes.api.model.Event;\nimport io.fabric8.kubernetes.api.model.EventBuilder;\nimport io.fabric8.kubernetes.api.model.ObjectReference;\nimport io.fabric8.kubernetes.api.model.ObjectReferenceBuilder;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\n\nimport java.text.DateFormat;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.TimeZone;\n\n@Service\npublic class EventNotification {\n\n    private static final Logger log = LoggerFactory.getLogger(EventNotification.class);\n\n    private final String crdGroup;\n    private final KubernetesClient client;\n\n    public EventNotification(@Value(\"${kubernetes.crd.group}\") String crdGroup, KubernetesClient client) {\n        this.crdGroup = crdGroup;\n        this.client = client;\n    }\n\n    public void storeNewEvent(EventType type, String message, Vault resource) {\n        // @deprecated in 1.25 event v1beta1 will be deprecated\n        ObjectReference ref = new ObjectReferenceBuilder()\n                .withName(resource.getMetadata().getName())\n                .withNamespace(resource.getMetadata().getNamespace())\n                .withApiVersion(crdGroup + \"/v1\")\n                .withKind(\"Vault\")\n                .withUid(resource.getMetadata().getUid())\n                .build();\n\n        TimeZone tz = TimeZone.getTimeZone(\"UTC\");\n        DateFormat df = new SimpleDateFormat(\"yyyy-MM-dd'T'HH:mm:ss'Z'\"); // Quoted \"Z\" to indicate UTC, no timezone offset\n        df.setTimeZone(tz);\n        String nowAsISO = df.format(new Date());\n\n        Event evt = new EventBuilder()\n                .withNewMetadata()\n                .withGenerateName(resource.getMetadata().getName())\n                .withNamespace(resource.getMetadata().getNamespace())\n                .endMetadata()\n                .withInvolvedObject(ref)\n                .withLastTimestamp(nowAsISO)\n                .withFirstTimestamp(nowAsISO)\n                .withReportingComponent(\"vault-crd\")\n                .withType(type.getEventType())\n                .withReason(type.getReason())\n                .withMessage(message)\n                .build();\n\n        try {\n            client.v1().events().inNamespace(resource.getMetadata().getNamespace()).resource(evt).create();\n        } catch (Exception ex) {\n            log.error(\"Failed to store event for {} in namespace {} next to resource with error\",\n                    resource.getMetadata().getName(), resource.getMetadata().getNamespace(), ex);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/kubernetes/event/EventType.java",
    "content": "package de.koudingspawn.vault.kubernetes.event;\n\npublic enum EventType {\n    CREATION_SUCCESSFUL(\"Normal\", \"SuccessfulCreated\"),\n    CREATION_FAILED(\"Failure\", \"FailedCreation\"),\n    MODIFICATION_SUCCESSFUL(\"Normal\", \"SuccessfulModified\"),\n    MODIFICATION_FAILED(\"Failure\", \"FailedModification\"),\n    ROTATION(\"Rotation\", \"RotationTriggered\"),\n    FIXED_REFERENCE(\"Normal\", \"FixedOwnerReference\"),\n    DELETION(\"Normal\", \"DeletionOfResource\");\n\n    private final String type;\n    private final String reason;\n\n    EventType(String type, String reason) {\n        this.type = type;\n        this.reason = reason;\n    }\n\n    public String getEventType() {\n        return type;\n    }\n\n    public String getReason() {\n        return reason;\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/kubernetes/scheduler/RefreshConfiguration.java",
    "content": "package de.koudingspawn.vault.kubernetes.scheduler;\n\nimport org.springframework.beans.factory.config.ServiceLocatorFactoryBean;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration\npublic class RefreshConfiguration {\n\n    @Bean(\"typeRefreshFactory\")\n    public ServiceLocatorFactoryBean slfbForTypeRefresh() {\n        ServiceLocatorFactoryBean slfb = new ServiceLocatorFactoryBean();\n        slfb.setServiceLocatorInterface(TypeRefreshFactory.class);\n        return slfb;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/kubernetes/scheduler/RequiresRefresh.java",
    "content": "package de.koudingspawn.vault.kubernetes.scheduler;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\n\npublic interface RequiresRefresh {\n\n    boolean refreshIsNeeded(Vault resource) throws SecretNotAccessibleException;\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/kubernetes/scheduler/ScheduledRefresh.java",
    "content": "package de.koudingspawn.vault.kubernetes.scheduler;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.kubernetes.EventHandler;\nimport de.koudingspawn.vault.kubernetes.event.EventNotification;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport static de.koudingspawn.vault.kubernetes.event.EventType.MODIFICATION_FAILED;\n\n@Component\npublic class ScheduledRefresh {\n\n    private static final Logger log = LoggerFactory.getLogger(ScheduledRefresh.class);\n\n    private final TypeRefreshFactory typeRefreshFactory;\n    private final EventHandler eventHandler;\n    private final EventNotification eventNotification;\n\n    public ScheduledRefresh(\n            EventHandler eventHandler,\n            TypeRefreshFactory typeRefreshFactory,\n            EventNotification eventNotification) {\n        this.typeRefreshFactory = typeRefreshFactory;\n        this.eventHandler = eventHandler;\n        this.eventNotification = eventNotification;\n    }\n\n    public void refreshVaultResource(Vault resource) {\n        RequiresRefresh requiresRefresh = typeRefreshFactory.get(resource.getSpec().getType().toString());\n        try {\n            if (requiresRefresh.refreshIsNeeded(resource)) {\n                log.info(\"Executing scheduled refresh for {} in namespace {}\", resource.getMetadata().getName(), resource.getMetadata().getNamespace());\n                eventHandler.modifyHandler(resource);\n            }\n        } catch (SecretNotAccessibleException e) {\n            log.info(\"Refresh of secret {} in namespace {} failed with exception\", resource.getMetadata().getName(), resource.getMetadata().getNamespace(), e);\n\n            eventNotification.storeNewEvent(MODIFICATION_FAILED, \"Modification of secret failed with exception \" + e.getMessage(), resource);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/kubernetes/scheduler/TypeRefreshFactory.java",
    "content": "package de.koudingspawn.vault.kubernetes.scheduler;\n\npublic interface TypeRefreshFactory {\n\n    RequiresRefresh get(String name);\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/kubernetes/scheduler/impl/CertJksRefresh.java",
    "content": "package de.koudingspawn.vault.kubernetes.scheduler.impl;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.kubernetes.scheduler.RequiresRefresh;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport org.springframework.stereotype.Component;\n\n@Component(\"CERTJKS\")\npublic class CertJksRefresh implements RequiresRefresh {\n\n    private final CertRefresh certRefresh;\n\n    public CertJksRefresh(CertRefresh certRefresh) {\n        this.certRefresh = certRefresh;\n    }\n\n    @Override\n    public boolean refreshIsNeeded(Vault resource) throws SecretNotAccessibleException {\n        return certRefresh.refreshIsNeeded(resource);\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/kubernetes/scheduler/impl/CertRefresh.java",
    "content": "package de.koudingspawn.vault.kubernetes.scheduler.impl;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.kubernetes.KubernetesService;\nimport de.koudingspawn.vault.kubernetes.scheduler.RequiresRefresh;\nimport de.koudingspawn.vault.vault.TypedSecretGenerator;\nimport de.koudingspawn.vault.vault.TypedSecretGeneratorFactory;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport io.fabric8.kubernetes.api.model.Secret;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Component;\n\n@Component(\"CERT\")\npublic class CertRefresh extends CompareHash implements RequiresRefresh {\n\n    private final String crdName;\n    private final KubernetesService kubernetesService;\n    private final TypedSecretGeneratorFactory typedSecretGeneratorFactory;\n\n    public CertRefresh(@Value(\"${kubernetes.crd.name}\") String crdName,\n                       KubernetesService kubernetesService,\n                       TypedSecretGeneratorFactory typedSecretGeneratorFactory) {\n        this.crdName = crdName;\n        this.typedSecretGeneratorFactory = typedSecretGeneratorFactory;\n        this.kubernetesService = kubernetesService;\n    }\n\n    @Override\n    public boolean refreshIsNeeded(Vault resource) throws SecretNotAccessibleException {\n        return certHashHasChanged(resource);\n    }\n\n    private boolean certHashHasChanged(Vault resource) throws SecretNotAccessibleException {\n        Secret secretByVault = kubernetesService.getSecretByVault(resource);\n        TypedSecretGenerator certGenerator = typedSecretGeneratorFactory.get(\"CERTGENERATOR\");\n        String vaultSha256 = certGenerator.getHash(resource.getSpec());\n\n        return super.hashHasChanged(secretByVault, vaultSha256, crdName);\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/kubernetes/scheduler/impl/CompareHash.java",
    "content": "package de.koudingspawn.vault.kubernetes.scheduler.impl;\n\nimport io.fabric8.kubernetes.api.model.Secret;\nimport org.springframework.util.StringUtils;\n\nimport static de.koudingspawn.vault.Constants.COMPARE_ANNOTATION;\n\nabstract public class CompareHash {\n\n    boolean hashHasChanged(Secret secretByVault, String vaultSha256, String crdName) {\n        if (secretByVault == null) {\n            // secret is not available...\n            return true;\n        }\n\n        if (secretByVault.getMetadata().getAnnotations() != null) {\n            String kubernetesSha256 = secretByVault.getMetadata().getAnnotations().get(crdName + COMPARE_ANNOTATION);\n\n            if (!StringUtils.hasText(kubernetesSha256)) {\n                // has no sha256 then calculate it\n                return true;\n            }\n\n            // check if vault and kubernetes are identical\n            return !vaultSha256.equals(kubernetesSha256);\n        }\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/kubernetes/scheduler/impl/DockerCfgRefresh.java",
    "content": "package de.koudingspawn.vault.kubernetes.scheduler.impl;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.kubernetes.KubernetesService;\nimport de.koudingspawn.vault.kubernetes.scheduler.RequiresRefresh;\nimport de.koudingspawn.vault.vault.TypedSecretGenerator;\nimport de.koudingspawn.vault.vault.TypedSecretGeneratorFactory;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport io.fabric8.kubernetes.api.model.Secret;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Component;\n\n@Component(\"DOCKERCFG\")\npublic class DockerCfgRefresh extends CompareHash implements RequiresRefresh {\n\n    private final String crdName;\n    private final KubernetesService kubernetesService;\n    private final TypedSecretGeneratorFactory typedSecretGeneratorFactory;\n\n\n    public DockerCfgRefresh(@Value(\"${kubernetes.crd.name}\") String crdName,\n                            KubernetesService kubernetesService,\n                            TypedSecretGeneratorFactory typedSecretGeneratorFactory) {\n        this.crdName = crdName;\n        this.kubernetesService = kubernetesService;\n        this.typedSecretGeneratorFactory = typedSecretGeneratorFactory;\n    }\n\n    public boolean refreshIsNeeded(Vault resource) throws SecretNotAccessibleException {\n        return dockerCfgHashHasChanged(resource);\n    }\n\n    private boolean dockerCfgHashHasChanged(Vault resource) throws SecretNotAccessibleException {\n        Secret secretByVault = kubernetesService.getSecretByVault(resource);\n        TypedSecretGenerator dockercfg = typedSecretGeneratorFactory.get(\"DOCKERCFGGENERATOR\");\n        String vaultSha256 = dockercfg.getHash(resource.getSpec());\n\n        return super.hashHasChanged(secretByVault, vaultSha256, crdName);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/kubernetes/scheduler/impl/KeyValueRefresh.java",
    "content": "package de.koudingspawn.vault.kubernetes.scheduler.impl;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.kubernetes.KubernetesService;\nimport de.koudingspawn.vault.kubernetes.scheduler.RequiresRefresh;\nimport de.koudingspawn.vault.vault.TypedSecretGeneratorFactory;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport io.fabric8.kubernetes.api.model.Secret;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Component;\n\n@Component(\"KEYVALUE\")\npublic class KeyValueRefresh extends CompareHash implements RequiresRefresh {\n\n    private final String crdName;\n    private final KubernetesService kubernetesService;\n    private final TypedSecretGeneratorFactory typedSecretGeneratorFactory;\n\n    public KeyValueRefresh(@Value(\"${kubernetes.crd.name}\") String crdName,\n                           KubernetesService kubernetesService,\n                           TypedSecretGeneratorFactory typedSecretGeneratorFactory) {\n        this.crdName = crdName;\n        this.kubernetesService = kubernetesService;\n        this.typedSecretGeneratorFactory = typedSecretGeneratorFactory;\n    }\n\n    public boolean refreshIsNeeded(Vault resource) throws SecretNotAccessibleException {\n        return certHashHasChanged(resource);\n    }\n\n    private boolean certHashHasChanged(Vault resource) throws SecretNotAccessibleException {\n        Secret secretByVault = kubernetesService.getSecretByVault(resource);\n        String vaultSha256 = typedSecretGeneratorFactory.get(\"KEYVALUEGENERATOR\").getHash(resource.getSpec());\n\n        return super.hashHasChanged(secretByVault, vaultSha256, crdName);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/kubernetes/scheduler/impl/KeyValueV2Refresh.java",
    "content": "package de.koudingspawn.vault.kubernetes.scheduler.impl;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.kubernetes.KubernetesService;\nimport de.koudingspawn.vault.kubernetes.scheduler.RequiresRefresh;\nimport de.koudingspawn.vault.vault.TypedSecretGeneratorFactory;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport io.fabric8.kubernetes.api.model.Secret;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Component;\n\n@Component(\"KEYVALUEV2\")\npublic class KeyValueV2Refresh extends CompareHash implements RequiresRefresh {\n\n    private final String crdName;\n    private final KubernetesService kubernetesService;\n    private final TypedSecretGeneratorFactory typedSecretGeneratorFactory;\n\n    public KeyValueV2Refresh(@Value(\"${kubernetes.crd.name}\") String crdName,\n                             KubernetesService kubernetesService,\n                             TypedSecretGeneratorFactory typedSecretGeneratorFactory) {\n        this.crdName = crdName;\n        this.kubernetesService = kubernetesService;\n        this.typedSecretGeneratorFactory = typedSecretGeneratorFactory;\n    }\n\n    public boolean refreshIsNeeded(Vault resource) throws SecretNotAccessibleException {\n        return hashHasChanged(resource);\n    }\n\n    private boolean hashHasChanged(Vault resource) throws SecretNotAccessibleException {\n        Secret secretByVault = kubernetesService.getSecretByVault(resource);\n        String vaultSha256 = typedSecretGeneratorFactory.get(\"KEYVALUEV2GENERATOR\").getHash(resource.getSpec());\n\n        return super.hashHasChanged(secretByVault, vaultSha256, crdName);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/kubernetes/scheduler/impl/PkiJksRefresh.java",
    "content": "package de.koudingspawn.vault.kubernetes.scheduler.impl;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.kubernetes.scheduler.RequiresRefresh;\nimport org.springframework.stereotype.Component;\n\n@Component(\"PKIJKS\")\npublic class PkiJksRefresh implements RequiresRefresh {\n\n    private final PkiRefresh pkiRefresh;\n\n    public PkiJksRefresh(PkiRefresh pkiRefresh) {\n        this.pkiRefresh = pkiRefresh;\n    }\n\n    @Override\n    public boolean refreshIsNeeded(Vault resource) {\n        return pkiRefresh.refreshIsNeeded(resource);\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/kubernetes/scheduler/impl/PkiRefresh.java",
    "content": "package de.koudingspawn.vault.kubernetes.scheduler.impl;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.kubernetes.KubernetesService;\nimport de.koudingspawn.vault.kubernetes.scheduler.RequiresRefresh;\nimport io.fabric8.kubernetes.api.model.Secret;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Component;\n\nimport java.text.ParseException;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.Optional;\nimport java.util.TimeZone;\n\nimport static de.koudingspawn.vault.Constants.COMPARE_ANNOTATION;\nimport static de.koudingspawn.vault.Constants.DATE_FORMAT;\n\n@Component(\"PKI\")\npublic class PkiRefresh implements RequiresRefresh {\n\n    private static final Logger log = LoggerFactory.getLogger(PkiRefresh.class);\n\n    private final int interval;\n    private final KubernetesService kubernetesService;\n    private final String crdName;\n\n    public PkiRefresh(@Value(\"${kubernetes.interval}\") int interval, @Value(\"${kubernetes.crd.name}\") String crdName, KubernetesService kubernetesService) {\n        this.interval = interval;\n        this.kubernetesService = kubernetesService;\n        this.crdName = crdName;\n    }\n\n    public boolean refreshIsNeeded(Vault resource) {\n        Secret secretByVault = kubernetesService.getSecretByVault(resource);\n        return secretByVault == null || certificateIsNearExpirationDate(secretByVault);\n    }\n\n    private boolean certificateIsNearExpirationDate(Secret secretByVault) {\n\n        if (secretByVault.getMetadata().getAnnotations() != null) {\n            String expiration = secretByVault.getMetadata().getAnnotations().get(crdName + COMPARE_ANNOTATION);\n\n            Optional<Date> expirationDate = parseDate(expiration);\n            if (expirationDate.isPresent()) {\n\n                Date nextIntervals = new Date();\n                nextIntervals.setTime(nextIntervals.getTime() + (interval * 1000 * 5));\n\n                return nextIntervals.after(expirationDate.get());\n            } else {\n                log.error(\"Failed to parse date of secret {} in namespace {}\", secretByVault.getMetadata().getName(), secretByVault.getMetadata().getNamespace());\n            }\n        }\n\n        return true;\n    }\n\n    private Optional<Date> parseDate(String date) {\n        if (date == null) {\n            return Optional.empty();\n        }\n\n        try {\n            SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT);\n            TimeZone tz = TimeZone.getTimeZone(\"UTC\");\n            format.setTimeZone(tz);\n\n            return Optional.of(format.parse(date));\n        } catch (ParseException e) {\n            return Optional.empty();\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/kubernetes/scheduler/impl/PropertiesRefresh.java",
    "content": "package de.koudingspawn.vault.kubernetes.scheduler.impl;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.kubernetes.scheduler.RequiresRefresh;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport org.springframework.stereotype.Component;\n\n@Component(\"PROPERTIES\")\npublic class PropertiesRefresh implements RequiresRefresh {\n\n    @Override\n    public boolean refreshIsNeeded(Vault resource) throws SecretNotAccessibleException {\n        //TODO: allow properties refresh\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/TypedSecretGenerator.java",
    "content": "package de.koudingspawn.vault.vault;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.crd.VaultSpec;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\n\npublic interface TypedSecretGenerator {\n\n    VaultSecret generateSecret(Vault resource) throws SecretNotAccessibleException;\n\n    String getHash(VaultSpec spec) throws SecretNotAccessibleException;\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/TypedSecretGeneratorFactory.java",
    "content": "package de.koudingspawn.vault.vault;\n\npublic interface TypedSecretGeneratorFactory {\n\n    TypedSecretGenerator get(String name);\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/VaultCommunication.java",
    "content": "package de.koudingspawn.vault.vault;\n\nimport de.koudingspawn.vault.crd.VaultDockerCfgConfiguration;\nimport de.koudingspawn.vault.crd.VaultPkiConfiguration;\nimport de.koudingspawn.vault.crd.VaultType;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport de.koudingspawn.vault.vault.communication.TokenLookup;\nimport de.koudingspawn.vault.vault.impl.dockercfg.PullSecret;\nimport de.koudingspawn.vault.vault.impl.pki.PKIRequest;\nimport de.koudingspawn.vault.vault.impl.pki.PKIResponse;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.StringUtils;\nimport org.springframework.vault.VaultException;\nimport org.springframework.vault.core.VaultTemplate;\nimport org.springframework.vault.core.VaultVersionedKeyValueOperations;\nimport org.springframework.vault.support.VaultResponseSupport;\nimport org.springframework.vault.support.Versioned;\nimport org.springframework.vault.support.Versioned.Version;\nimport org.springframework.web.client.HttpStatusCodeException;\nimport org.springframework.web.client.RestClientException;\nimport org.springframework.web.client.RestOperations;\n\nimport java.util.HashMap;\nimport java.util.Optional;\nimport java.util.regex.Pattern;\n\n@Component\npublic class VaultCommunication {\n\n    private static final Logger log = LoggerFactory.getLogger(VaultCommunication.class);\n    private static final Pattern keyValuePattern = Pattern.compile(\"^.*?\\\\/.*?$\");\n\n    private final VaultTemplate vaultTemplate;\n\n    public VaultCommunication(VaultTemplate vaultTemplate) {\n        this.vaultTemplate = vaultTemplate;\n    }\n\n    public PKIResponse createPki(String path, VaultPkiConfiguration configuration) throws SecretNotAccessibleException {\n        PKIRequest pkiRequest = generateRequest(configuration);\n\n        HttpEntity<PKIRequest> requestEntity = new HttpEntity<>(pkiRequest);\n        try {\n            return vaultTemplate.doWithSession(restOperations -> restOperations.postForObject(path, requestEntity, PKIResponse.class));\n        } catch (HttpStatusCodeException exception) {\n            int statusCode = exception.getStatusCode().value();\n\n            throw new SecretNotAccessibleException(\n                    String.format(\"Couldn't generate pki secret from vault path %s status code %d\", path, statusCode));\n        } catch (RestClientException ex) {\n            throw new SecretNotAccessibleException(\"Couldn't communicate with vault\", ex);\n        }\n    }\n\n    public PKIResponse getCert(String path) throws SecretNotAccessibleException {\n        return getRequest(path, PKIResponse.class);\n    }\n\n    public PullSecret getDockerCfg(String path, VaultDockerCfgConfiguration dockerCfgConfiguration) throws SecretNotAccessibleException {\n        if (dockerCfgConfiguration.getType().equals(VaultType.KEYVALUE)) {\n            return getRequest(path, PullSecret.class);\n        } else {\n            return getVersionedSecret(path, Optional.ofNullable(dockerCfgConfiguration.getVersion()), PullSecret.class);\n        }\n    }\n\n    public HashMap getKeyValue(String path) throws SecretNotAccessibleException {\n        return getRequest(path, HashMap.class);\n    }\n\n    private <T> T getRequest(String path, Class<T> clazz) throws SecretNotAccessibleException {\n        try {\n            VaultResponseSupport<T> response = vaultTemplate.read(path, clazz);\n            if (response != null) {\n                return response.getData();\n            } else {\n                throw new SecretNotAccessibleException(String.format(\"The secret %s is not available or in the wrong format.\", path));\n            }\n        } catch (VaultException exception) {\n            throw new SecretNotAccessibleException(\n                    String.format(\"Couldn't load secret from vault path %s\", path), exception);\n        }\n    }\n\n    private PKIRequest generateRequest(VaultPkiConfiguration configuration) {\n        PKIRequest pkiRequest = new PKIRequest();\n\n        if (configuration != null) {\n            if (StringUtils.hasText(configuration.getCommonName())) {\n                pkiRequest.setCommon_name(configuration.getCommonName());\n            }\n            if (StringUtils.hasText(configuration.getAltNames())) {\n                pkiRequest.setAlt_names(configuration.getAltNames());\n            }\n            if (StringUtils.hasText(configuration.getIpSans())) {\n                pkiRequest.setIp_sans(configuration.getIpSans());\n            }\n            if (StringUtils.hasText(configuration.getTtl())) {\n                pkiRequest.setTtl(configuration.getTtl());\n            }\n        }\n\n        return pkiRequest;\n    }\n\n    public HashMap getVersionedSecret(String path, Optional<Integer> version) throws SecretNotAccessibleException {\n        return getVersionedSecret(path, version, HashMap.class);\n    }\n\n    private <T> T getVersionedSecret(String path, Optional<Integer> version, Class<T> clazz) throws SecretNotAccessibleException {\n        String mountPoint = extractMountPoint(path);\n        String extractedKey = extractKey(path);\n\n        VaultVersionedKeyValueOperations versionedKV = vaultTemplate.opsForVersionedKeyValue(mountPoint);\n        Versioned<T> versionedResponse;\n\n        try {\n            if (version.isPresent()) {\n                versionedResponse = versionedKV.get(extractedKey, Version.from(version.get()), clazz);\n            } else {\n                versionedResponse = versionedKV.get(extractedKey, clazz);\n            }\n\n            if (versionedResponse != null) {\n                return versionedResponse.getData();\n            }\n\n            throw new SecretNotAccessibleException(String.format(\"The secret %s is not available or in the wrong format.\", path));\n\n        } catch (VaultException ex) {\n            throw new SecretNotAccessibleException(\n                    String.format(\"Couldn't load secret from vault path %s\", path), ex);\n        }\n    }\n\n    public boolean isHealthy() {\n        return vaultTemplate.doWithSession(this::doWithRestOperations);\n    }\n\n    private boolean doWithRestOperations(RestOperations restOperations) {\n        try {\n            ResponseEntity<TokenLookup> healthEntity = restOperations.getForEntity(\"/auth/token/lookup-self\", TokenLookup.class);\n            return healthEntity.getStatusCode().is2xxSuccessful();\n        } catch (RestClientException ex) {\n            log.error(\"Vault health check failed!\", ex);\n            return false;\n        }\n    }\n\n    private String extractMountPoint(String path) throws SecretNotAccessibleException {\n        if (keyValuePattern.matcher(path).matches()) {\n            return path.split(\"/\", 2)[0];\n        }\n\n        throw new SecretNotAccessibleException(String.format(\"Could not extract mountpoint from path: %s. A valid path looks like 'mountpoint/key'\", path));\n    }\n\n    private String extractKey(String path) throws SecretNotAccessibleException {\n        if (keyValuePattern.matcher(path).matches()) {\n            return path.split(\"/\", 2)[1];\n        }\n\n        throw new SecretNotAccessibleException(String.format(\"Could not extract key from path: %s. A valid path looks like 'mountpoint/key'\", path));\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/VaultConfiguration.java",
    "content": "package de.koudingspawn.vault.vault;\n\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.beans.factory.config.ServiceLocatorFactoryBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.vault.authentication.ClientAuthentication;\nimport org.springframework.vault.authentication.KubernetesAuthentication;\nimport org.springframework.vault.authentication.KubernetesAuthenticationOptions;\nimport org.springframework.vault.authentication.TokenAuthentication;\nimport org.springframework.vault.client.VaultEndpoint;\nimport org.springframework.vault.config.AbstractVaultConfiguration;\n\nimport java.net.URI;\n\n@Configuration\npublic class VaultConfiguration {\n\n    @Bean\n    public ServiceLocatorFactoryBean slfbForTypeRefresh() {\n        ServiceLocatorFactoryBean slfb = new ServiceLocatorFactoryBean();\n        slfb.setServiceLocatorInterface(TypedSecretGeneratorFactory.class);\n        return slfb;\n    }\n\n    @Configuration\n    @ConditionalOnProperty(name = \"kubernetes.vault.auth\", havingValue = \"token\")\n    class VaultTokenConnection extends AbstractVaultConfiguration {\n\n        private final String vaultToken;\n        private final String vaultUrl;\n\n        VaultTokenConnection(@Value(\"${kubernetes.vault.token}\") String vaultToken,\n                             @Value(\"${kubernetes.vault.url}\") String vaultUrl) {\n            this.vaultToken = vaultToken;\n            this.vaultUrl = vaultUrl;\n        }\n\n        @Override\n        public VaultEndpoint vaultEndpoint() {\n            return VaultEndpoint.from(getVaultUrlWithoutPath(vaultUrl));\n        }\n\n        @Override\n        public ClientAuthentication clientAuthentication() {\n            return new TokenAuthentication(vaultToken);\n        }\n\n    }\n\n    @Configuration\n    @ConditionalOnProperty(name = \"kubernetes.vault.auth\", havingValue = \"serviceAccount\")\n    class VaultServiceAccountConnection extends AbstractVaultConfiguration {\n\n        private final String vaultUrl;\n        private final String role;\n        private final String path;\n\n        VaultServiceAccountConnection(@Value(\"${kubernetes.vault.url}\") String vaultUrl,\n                                      @Value(\"${kubernetes.vault.role}\") String role,\n                                      @Value(\"${kubernetes.vault.path:kubernetes}\") String path) {\n            this.vaultUrl = vaultUrl;\n            this.role = role;\n            this.path = path;\n        }\n\n        @Override\n        public VaultEndpoint vaultEndpoint() {\n            return VaultEndpoint.from(getVaultUrlWithoutPath(vaultUrl));\n        }\n\n        @Override\n        public ClientAuthentication clientAuthentication() {\n            KubernetesAuthenticationOptions options =\n                    KubernetesAuthenticationOptions.builder().path(path).role(role).build();\n\n            return new KubernetesAuthentication(options, restOperations());\n        }\n    }\n\n    private URI getVaultUrlWithoutPath(String vaultUrl) {\n        return URI.create(vaultUrl.replace(\"/v1/\", \"\"));\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/VaultHealthCheck.java",
    "content": "package de.koudingspawn.vault.vault;\n\nimport org.springframework.boot.actuate.health.Health;\nimport org.springframework.boot.actuate.health.HealthIndicator;\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class VaultHealthCheck implements HealthIndicator {\n\n    private final VaultCommunication vaultCommunication;\n\n    public VaultHealthCheck(VaultCommunication vaultCommunication) {\n        this.vaultCommunication = vaultCommunication;\n    }\n\n    @Override\n    public Health health() {\n        Health.Builder healthBuilder = Health.down();\n\n        if (this.vaultCommunication.isHealthy()) {\n            healthBuilder.up();\n        }\n\n        return healthBuilder.build();\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/VaultSecret.java",
    "content": "package de.koudingspawn.vault.vault;\n\nimport java.util.Map;\n\npublic class VaultSecret {\n\n    private Map<String, String> data;\n\n    private String compare;\n\n    private String type;\n\n    public VaultSecret(Map<String, String> data, String compare) {\n        this.data = data;\n        this.compare = compare;\n        this.type = \"Opaque\";\n    }\n\n    public VaultSecret(Map<String, String> data, String compare, String type) {\n        this.data = data;\n        this.compare = compare;\n        this.type = type;\n    }\n\n    public Map<String, String> getData() {\n        return data;\n    }\n\n    public void setData(Map<String, String> data) {\n        this.data = data;\n    }\n\n    public String getCompare() {\n        return compare;\n    }\n\n    public void setCompare(String compare) {\n        this.compare = compare;\n    }\n\n    public String getType() {\n        return type;\n    }\n\n    public void setType(String type) {\n        this.type = type;\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/VaultService.java",
    "content": "package de.koudingspawn.vault.vault;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport org.springframework.stereotype.Component;\n\n@Component\npublic class VaultService {\n\n    private final TypedSecretGeneratorFactory typedSecretGeneratorFactory;\n\n    public VaultService(TypedSecretGeneratorFactory typedSecretGeneratorFactory) {\n        this.typedSecretGeneratorFactory = typedSecretGeneratorFactory;\n    }\n\n    public VaultSecret generateSecret(Vault resource) throws SecretNotAccessibleException {\n        TypedSecretGenerator typedSecretGenerator = typedSecretGeneratorFactory.get(resource.getSpec().getType().toString() + \"GENERATOR\");\n        return typedSecretGenerator.generateSecret(resource);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/communication/SecretNotAccessibleException.java",
    "content": "package de.koudingspawn.vault.vault.communication;\n\npublic class SecretNotAccessibleException extends Exception {\n\n    public SecretNotAccessibleException(String message) {\n        super(message);\n    }\n\n    public SecretNotAccessibleException(String message, Throwable cause) {\n        super(message, cause);\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/communication/TokenLookup.java",
    "content": "package de.koudingspawn.vault.vault.communication;\n\npublic class TokenLookup {\n\n    private String request_id;\n    private boolean renewable;\n    private TokenLookupData data;\n\n    public String getRequest_id() {\n        return request_id;\n    }\n\n    public void setRequest_id(String request_id) {\n        this.request_id = request_id;\n    }\n\n    public boolean isRenewable() {\n        return renewable;\n    }\n\n    public void setRenewable(boolean renewable) {\n        this.renewable = renewable;\n    }\n\n    public TokenLookupData getData() {\n        return data;\n    }\n\n    public void setData(TokenLookupData data) {\n        this.data = data;\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/communication/TokenLookupData.java",
    "content": "package de.koudingspawn.vault.vault.communication;\n\npublic class TokenLookupData {\n    private String accessor;\n    private String display_name;\n    private String id;\n    private String path;\n\n    public String getAccessor() {\n        return accessor;\n    }\n\n    public void setAccessor(String accessor) {\n        this.accessor = accessor;\n    }\n\n    public String getDisplay_name() {\n        return display_name;\n    }\n\n    public void setDisplay_name(String display_name) {\n        this.display_name = display_name;\n    }\n\n    public String getId() {\n        return id;\n    }\n\n    public void setId(String id) {\n        this.id = id;\n    }\n\n    public String getPath() {\n        return path;\n    }\n\n    public void setPath(String path) {\n        this.path = path;\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/impl/CertGenerator.java",
    "content": "package de.koudingspawn.vault.vault.impl;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.crd.VaultSpec;\nimport de.koudingspawn.vault.vault.TypedSecretGenerator;\nimport de.koudingspawn.vault.vault.VaultCommunication;\nimport de.koudingspawn.vault.vault.VaultSecret;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport de.koudingspawn.vault.vault.impl.pki.PKIResponse;\nimport de.koudingspawn.vault.vault.impl.pki.VaultResponseData;\nimport org.springframework.stereotype.Component;\n\n@Component(\"CERTGENERATOR\")\npublic class CertGenerator implements TypedSecretGenerator {\n\n    private final VaultCommunication vaultCommunication;\n    private final SharedVaultResponseMapper sharedVaultResponseMapper;\n\n    public CertGenerator(VaultCommunication vaultCommunication, SharedVaultResponseMapper sharedVaultResponseMapper) {\n        this.vaultCommunication = vaultCommunication;\n        this.sharedVaultResponseMapper = sharedVaultResponseMapper;\n    }\n\n    @Override\n    public VaultSecret generateSecret(Vault resource) throws SecretNotAccessibleException {\n        PKIResponse pkiResponse = vaultCommunication.getCert(resource.getSpec().getPath());\n\n        return sharedVaultResponseMapper.mapCert(pkiResponse.getData());\n    }\n\n    @Override\n    public String getHash(VaultSpec spec) throws SecretNotAccessibleException {\n        PKIResponse cert = vaultCommunication.getCert(spec.getPath());\n        if (cert != null && cert.getData() != null) {\n            VaultResponseData pkiResponse = cert.getData();\n            return sharedVaultResponseMapper.mapCert(pkiResponse).getCompare();\n        }\n\n        throw new SecretNotAccessibleException(\"Secret has no data field\");\n    }\n\n\n\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/impl/CertJksGenerator.java",
    "content": "package de.koudingspawn.vault.vault.impl;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.crd.VaultSpec;\nimport de.koudingspawn.vault.vault.TypedSecretGenerator;\nimport de.koudingspawn.vault.vault.VaultCommunication;\nimport de.koudingspawn.vault.vault.VaultSecret;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport de.koudingspawn.vault.vault.impl.pki.PKIResponse;\nimport org.springframework.stereotype.Component;\n\n@Component(\"CERTJKSGENERATOR\")\npublic class CertJksGenerator implements TypedSecretGenerator {\n\n    private final VaultCommunication vaultCommunication;\n    private final SharedVaultResponseMapper sharedVaultResponseMapper;\n\n    public CertJksGenerator(VaultCommunication vaultCommunication, SharedVaultResponseMapper sharedVaultResponseMapper) {\n        this.vaultCommunication = vaultCommunication;\n        this.sharedVaultResponseMapper = sharedVaultResponseMapper;\n    }\n\n    @Override\n    public VaultSecret generateSecret(Vault resource) throws SecretNotAccessibleException {\n        PKIResponse jksCert = vaultCommunication.getCert(resource.getSpec().getPath());\n        return sharedVaultResponseMapper.mapJks(jksCert.getData(), resource.getSpec().getJksConfiguration(), resource.getSpec().getType());\n    }\n\n    @Override\n    public String getHash(VaultSpec spec) {\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/impl/DockerCfgGenerator.java",
    "content": "package de.koudingspawn.vault.vault.impl;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.crd.VaultDockerCfgConfiguration;\nimport de.koudingspawn.vault.crd.VaultSpec;\nimport de.koudingspawn.vault.vault.TypedSecretGenerator;\nimport de.koudingspawn.vault.vault.VaultCommunication;\nimport de.koudingspawn.vault.vault.VaultSecret;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport de.koudingspawn.vault.vault.impl.dockercfg.PullSecret;\nimport org.springframework.stereotype.Component;\n\nimport java.util.Base64;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Optional;\n\n@Component(\"DOCKERCFGGENERATOR\")\npublic class DockerCfgGenerator implements TypedSecretGenerator {\n\n    private final VaultCommunication vaultCommunication;\n\n    public DockerCfgGenerator(VaultCommunication vaultCommunication) {\n        this.vaultCommunication = vaultCommunication;\n    }\n\n    @Override\n    public VaultSecret generateSecret(Vault resource) throws SecretNotAccessibleException {\n        PullSecret dockerCfg = vaultCommunication.getDockerCfg(resource.getSpec().getPath(), getConfiguration(resource.getSpec()));\n        return mapDockerCfg(dockerCfg);\n    }\n\n    @Override\n    public String getHash(VaultSpec spec) throws SecretNotAccessibleException {\n        PullSecret dockerCfg = vaultCommunication.getDockerCfg(spec.getPath(), getConfiguration(spec));\n        if (dockerCfg != null) {\n            return mapDockerCfg(dockerCfg).getCompare();\n        }\n\n        throw new SecretNotAccessibleException(\"Secret has no data field\");\n    }\n\n    private VaultDockerCfgConfiguration getConfiguration(VaultSpec spec) {\n        return Optional.ofNullable(spec.getDockerCfgConfiguration()).orElse(new VaultDockerCfgConfiguration());\n    }\n\n    private VaultSecret mapDockerCfg(PullSecret pullSecret) {\n        String dockerCfg = String.format(\"{\\\"%s\\\": {\\\"username\\\": \\\"%s\\\", \\\"password\\\": \\\"%s\\\", \\\"email\\\": \\\"%s\\\", \\\"auth\\\": \\\"%s\\\"}}\",\n                pullSecret.getUrl(), pullSecret.getUsername(), pullSecret.getPassword(), pullSecret.getEmail(), pullSecret.getAuth());\n\n        Map<String, String> data = new HashMap<>();\n        data.put(\".dockercfg\", Base64.getEncoder().encodeToString(dockerCfg.getBytes()));\n\n        return new VaultSecret(data, Sha256.generateSha256(dockerCfg), \"kubernetes.io/dockercfg\");\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/impl/EncryptionUtils.java",
    "content": "package de.koudingspawn.vault.vault.impl;\n\nimport java.security.GeneralSecurityException;\nimport java.security.KeyFactory;\nimport java.security.PrivateKey;\nimport java.security.spec.InvalidKeySpecException;\nimport java.security.spec.PKCS8EncodedKeySpec;\nimport java.util.Base64;\n\n\n// https://github.com/Mastercard/client-encryption-java/blob/44b38fbc8e9fd64d252cbbf47a4bc5208a8ae741/src/main/java/com/mastercard/developer/utils/EncryptionUtils.java#L95\npublic class EncryptionUtils {\n    private static final String PKCS_1_PEM_HEADER = \"-----BEGIN RSA PRIVATE KEY-----\";\n    private static final String PKCS_1_PEM_FOOTER = \"-----END RSA PRIVATE KEY-----\";\n    private static final String PKCS_8_PEM_HEADER = \"-----BEGIN PRIVATE KEY-----\";\n    private static final String PKCS_8_PEM_FOOTER = \"-----END PRIVATE KEY-----\";\n\n    private EncryptionUtils() {\n    }\n\n    /**\n     * Load a RSA decryption key from a file (PEM or DER).\n     */\n    public static PrivateKey loadPrivateKey(String keyDataString) throws GeneralSecurityException {\n\n        if (keyDataString.contains(PKCS_1_PEM_HEADER)) {\n            // OpenSSL / PKCS#1 Base64 PEM encoded file\n            keyDataString = keyDataString.replace(PKCS_1_PEM_HEADER, \"\");\n            keyDataString = keyDataString.replace(PKCS_1_PEM_FOOTER, \"\");\n            keyDataString = keyDataString.replace(\"\\n\", \"\");\n            keyDataString = keyDataString.replace(\"\\r\\n\", \"\");\n            return readPkcs1PrivateKey(Base64.getDecoder().decode(keyDataString));\n        }\n\n        if (keyDataString.contains(PKCS_8_PEM_HEADER)) {\n            // PKCS#8 Base64 PEM encoded file\n            keyDataString = keyDataString.replace(PKCS_8_PEM_HEADER, \"\");\n            keyDataString = keyDataString.replace(PKCS_8_PEM_FOOTER, \"\");\n            keyDataString = keyDataString.replace(\"\\n\", \"\");\n            keyDataString = keyDataString.replace(\"\\r\\n\", \"\");\n            return readPkcs8PrivateKey(Base64.getDecoder().decode(keyDataString));\n        }\n\n        throw new GeneralSecurityException(\"No parser for secret found\");\n    }\n\n    /**\n     * Create a PrivateKey instance from raw PKCS#8 bytes.\n     */\n    private static PrivateKey readPkcs8PrivateKey(byte[] pkcs8Bytes) throws GeneralSecurityException {\n        KeyFactory keyFactory = KeyFactory.getInstance(\"RSA\");\n        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes);\n        try {\n            return keyFactory.generatePrivate(keySpec);\n        } catch (InvalidKeySpecException e) {\n            throw new IllegalArgumentException(\"Unexpected key format!\", e);\n        }\n    }\n\n    /**\n     * Create a PrivateKey instance from raw PKCS#1 bytes.\n     */\n    private static PrivateKey readPkcs1PrivateKey(byte[] pkcs1Bytes) throws GeneralSecurityException {\n        // We can't use Java internal APIs to parse ASN.1 structures, so we build a PKCS#8 key Java can understand\n        int pkcs1Length = pkcs1Bytes.length;\n        int totalLength = pkcs1Length + 22;\n        byte[] pkcs8Header = new byte[]{\n                0x30, (byte) 0x82, (byte) ((totalLength >> 8) & 0xff), (byte) (totalLength & 0xff), // Sequence + total length\n                0x2, 0x1, 0x0, // Integer (0)\n                0x30, 0xD, 0x6, 0x9, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0xD, 0x1, 0x1, 0x1, 0x5, 0x0, // Sequence: 1.2.840.113549.1.1.1, NULL\n                0x4, (byte) 0x82, (byte) ((pkcs1Length >> 8) & 0xff), (byte) (pkcs1Length & 0xff) // Octet string + length\n        };\n        byte[] pkcs8bytes = join(pkcs8Header, pkcs1Bytes);\n        return readPkcs8PrivateKey(pkcs8bytes);\n    }\n\n    private static byte[] join(byte[] byteArray1, byte[] byteArray2) {\n        byte[] bytes = new byte[byteArray1.length + byteArray2.length];\n        System.arraycopy(byteArray1, 0, bytes, 0, byteArray1.length);\n        System.arraycopy(byteArray2, 0, bytes, byteArray1.length, byteArray2.length);\n        return bytes;\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/impl/KeyValueGenerator.java",
    "content": "package de.koudingspawn.vault.vault.impl;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.crd.VaultSpec;\nimport de.koudingspawn.vault.vault.TypedSecretGenerator;\nimport de.koudingspawn.vault.vault.VaultCommunication;\nimport de.koudingspawn.vault.vault.VaultSecret;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport org.springframework.stereotype.Component;\n\nimport java.util.Base64;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.TreeMap;\nimport java.util.stream.Collectors;\n\n@Component(\"KEYVALUEGENERATOR\")\npublic class KeyValueGenerator implements TypedSecretGenerator {\n\n    private final VaultCommunication vaultCommunication;\n\n    public KeyValueGenerator(VaultCommunication vaultCommunication) {\n        this.vaultCommunication = vaultCommunication;\n    }\n\n    @Override\n    public VaultSecret generateSecret(Vault resource) throws SecretNotAccessibleException {\n        HashMap keyValueResponse = vaultCommunication.getKeyValue(resource.getSpec().getPath());\n        return mapKeyValueResponse(keyValueResponse);\n    }\n\n    @Override\n    public String getHash(VaultSpec resource) throws SecretNotAccessibleException {\n        HashMap keyValue = vaultCommunication.getKeyValue(resource.getPath());\n        if (keyValue != null) {\n            return mapKeyValueResponse(keyValue).getCompare();\n        }\n\n        throw new SecretNotAccessibleException(\"Secret has no data field\");\n    }\n\n    private VaultSecret mapKeyValueResponse(HashMap<String, String> keyValue) {\n        TreeMap<String, String> sortedMap = new TreeMap<>(keyValue);\n\n        Map<String, String> base64Encoded = sortedMap.entrySet()\n                .stream()\n                .collect(Collectors\n                        .toMap(Map.Entry::getKey,\n                                e -> Base64.getEncoder().encodeToString(e.getValue().getBytes())));\n\n        String sha256 = Sha256.generateSha256(base64Encoded.values().toArray(new String[0]));\n\n        return new VaultSecret(base64Encoded, sha256);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/impl/KeyValueV2Generator.java",
    "content": "package de.koudingspawn.vault.vault.impl;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.crd.VaultSpec;\nimport de.koudingspawn.vault.vault.TypedSecretGenerator;\nimport de.koudingspawn.vault.vault.VaultCommunication;\nimport de.koudingspawn.vault.vault.VaultSecret;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport org.springframework.stereotype.Component;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n@Component(\"KEYVALUEV2GENERATOR\")\npublic class KeyValueV2Generator implements TypedSecretGenerator {\n\n    private final VaultCommunication vaultCommunication;\n\n    public KeyValueV2Generator(VaultCommunication vaultCommunication) {\n        this.vaultCommunication = vaultCommunication;\n    }\n\n    @Override\n    public VaultSecret generateSecret(Vault resource) throws SecretNotAccessibleException {\n        Optional<Integer> version = getVersion(resource.getSpec());\n        HashMap versionedKVResponse =\n                vaultCommunication.getVersionedSecret(resource.getSpec().getPath(), version);\n        return mapKeyValueResponse(versionedKVResponse);\n    }\n\n    @Override\n    public String getHash(VaultSpec resource) throws SecretNotAccessibleException {\n        Optional<Integer> version = getVersion(resource);\n        HashMap keyValue = vaultCommunication.getVersionedSecret(resource.getPath(), version);\n        if (keyValue != null) {\n            return mapKeyValueResponse(keyValue).getCompare();\n        }\n\n        throw new SecretNotAccessibleException(\"Secret has no data field\");\n    }\n\n    private VaultSecret mapKeyValueResponse(HashMap<String, String> keyValue) {\n        TreeMap<String, String> sortedMap = new TreeMap<>(keyValue);\n\n        Map<String, String> base64Encoded = sortedMap.entrySet()\n                .stream()\n                .collect(Collectors\n                        .toMap(Map.Entry::getKey,\n                                e -> Base64.getEncoder().encodeToString(e.getValue().getBytes())));\n\n        String sha256 = Sha256.generateSha256(base64Encoded.values().toArray(new String[0]));\n\n        return new VaultSecret(base64Encoded, sha256);\n    }\n\n    private Optional<Integer> getVersion(VaultSpec resource) {\n        if (resource.getVersionConfiguration() != null ) {\n            return Optional.ofNullable(resource.getVersionConfiguration().getVersion());\n        }\n        return Optional.empty();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/impl/PkiJksGenerator.java",
    "content": "package de.koudingspawn.vault.vault.impl;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.crd.VaultSpec;\nimport de.koudingspawn.vault.vault.TypedSecretGenerator;\nimport de.koudingspawn.vault.vault.VaultCommunication;\nimport de.koudingspawn.vault.vault.VaultSecret;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport de.koudingspawn.vault.vault.impl.pki.PKIResponse;\nimport org.springframework.stereotype.Component;\n\n@Component(\"PKIJKSGENERATOR\")\npublic class PkiJksGenerator implements TypedSecretGenerator {\n\n    private final VaultCommunication vaultCommunication;\n    private final SharedVaultResponseMapper sharedVaultResponseMapper;\n\n    public PkiJksGenerator(VaultCommunication vaultCommunication, SharedVaultResponseMapper sharedVaultResponseMapper) {\n        this.vaultCommunication = vaultCommunication;\n        this.sharedVaultResponseMapper = sharedVaultResponseMapper;\n    }\n\n    @Override\n    public VaultSecret generateSecret(Vault resource) throws SecretNotAccessibleException {\n        PKIResponse jksPki = vaultCommunication.createPki(resource.getSpec().getPath(), resource.getSpec().getPkiConfiguration());\n        return sharedVaultResponseMapper.mapJks(jksPki.getData(), resource.getSpec().getJksConfiguration(), resource.getSpec().getType());\n    }\n\n    @Override\n    public String getHash(VaultSpec spec) {\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/impl/PkiSecretGenerator.java",
    "content": "package de.koudingspawn.vault.vault.impl;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.crd.VaultSpec;\nimport de.koudingspawn.vault.vault.TypedSecretGenerator;\nimport de.koudingspawn.vault.vault.VaultCommunication;\nimport de.koudingspawn.vault.vault.VaultSecret;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport de.koudingspawn.vault.vault.impl.pki.PKIResponse;\nimport org.springframework.stereotype.Component;\n\n@Component(\"PKIGENERATOR\")\npublic class PkiSecretGenerator implements TypedSecretGenerator {\n\n    private final VaultCommunication vaultCommunication;\n    private final SharedVaultResponseMapper sharedVaultResponseMapper;\n\n    public PkiSecretGenerator(VaultCommunication vaultCommunication, SharedVaultResponseMapper sharedVaultResponseMapper) {\n        this.vaultCommunication = vaultCommunication;\n        this.sharedVaultResponseMapper = sharedVaultResponseMapper;\n    }\n\n    @Override\n    public VaultSecret generateSecret(Vault resource) throws SecretNotAccessibleException {\n        PKIResponse pki = vaultCommunication.createPki(resource.getSpec().getPath(), resource.getSpec().getPkiConfiguration());\n\n        return sharedVaultResponseMapper.mapPki(pki.getData());\n    }\n\n    @Override\n    public String getHash(VaultSpec spec) {\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/impl/PropertiesGenerator.java",
    "content": "package de.koudingspawn.vault.vault.impl;\n\nimport com.google.common.collect.Maps;\nimport com.hubspot.jinjava.Jinjava;\nimport com.hubspot.jinjava.interpret.FatalTemplateErrorsException;\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.crd.VaultPropertiesConfiguration;\nimport de.koudingspawn.vault.crd.VaultSpec;\nimport de.koudingspawn.vault.vault.TypedSecretGenerator;\nimport de.koudingspawn.vault.vault.VaultCommunication;\nimport de.koudingspawn.vault.vault.VaultSecret;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport de.koudingspawn.vault.vault.impl.properties.VaultJinjaLookup;\nimport org.springframework.stereotype.Component;\n\nimport java.util.Base64;\nimport java.util.HashMap;\nimport java.util.Map;\n\n@Component(\"PROPERTIESGENERATOR\")\npublic class PropertiesGenerator implements TypedSecretGenerator {\n\n    private final VaultCommunication vaultCommunication;\n\n    public PropertiesGenerator(VaultCommunication vaultCommunication) {\n        this.vaultCommunication = vaultCommunication;\n    }\n\n    @Override\n    public VaultSecret generateSecret(Vault resource) throws SecretNotAccessibleException {\n        VaultPropertiesConfiguration propertiesConfiguration = resource.getSpec().getPropertiesConfiguration();\n\n        if (propertiesConfiguration != null && propertiesConfiguration.getFiles() != null) {\n            Map<String, Object> context = Maps.newHashMap();\n            context.put(\"vault\", new VaultJinjaLookup(vaultCommunication));\n            if (propertiesConfiguration.getContext() != null) {\n                context.putAll(propertiesConfiguration.getContext());\n            }\n\n            try {\n                Map<String, String> renderedFiles = renderFiles(context, propertiesConfiguration.getFiles());\n                // TODO: support change in properties\n                return new VaultSecret(renderedFiles, \"COMPARE\");\n            } catch (FatalTemplateErrorsException ex) {\n                throw new SecretNotAccessibleException(ex.getMessage(), ex);\n            }\n        }\n\n        throw new SecretNotAccessibleException(\"Does not contain the required Files to render\");\n    }\n\n    @Override\n    public String getHash(VaultSpec spec) throws SecretNotAccessibleException {\n        return \"COMPARE\";\n    }\n\n    private Map<String, String> renderFiles(Map<String, Object> context, Map<String, String> files) throws FatalTemplateErrorsException {\n        Jinjava jinjava = new Jinjava();\n        Map<String, String> targetFiles = new HashMap<>();\n\n        files.forEach((key, value) -> {\n            String renderedContent = jinjava.render(value, context);\n            targetFiles.put(key, Base64.getEncoder().encodeToString(renderedContent.getBytes()));\n        });\n\n        return targetFiles;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/impl/Sha256.java",
    "content": "package de.koudingspawn.vault.vault.impl;\n\nimport java.nio.charset.StandardCharsets;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.Base64;\n\npublic class Sha256 {\n\n    public static String generateSha256(String ... args) {\n        try {\n            StringBuilder sb = new StringBuilder();\n            for (String arg : args) {\n                sb.append(arg).append(\";\");\n            }\n\n            MessageDigest digest = MessageDigest.getInstance(\"SHA-256\");\n            byte[] hash = digest.digest(sb.toString().getBytes(StandardCharsets.UTF_8));\n            return Base64.getEncoder().encodeToString(hash);\n        } catch (NoSuchAlgorithmException e) {\n            e.printStackTrace();\n\n            return null;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/impl/SharedVaultResponseMapper.java",
    "content": "package de.koudingspawn.vault.vault.impl;\n\nimport de.koudingspawn.vault.Constants;\nimport de.koudingspawn.vault.crd.VaultJKSConfiguration;\nimport de.koudingspawn.vault.crd.VaultType;\nimport de.koudingspawn.vault.vault.VaultSecret;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport de.koudingspawn.vault.vault.impl.pki.VaultResponseData;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.StringUtils;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.security.GeneralSecurityException;\nimport java.security.KeyStore;\nimport java.security.cert.Certificate;\nimport java.security.cert.CertificateException;\nimport java.security.cert.CertificateFactory;\nimport java.security.cert.X509Certificate;\nimport java.text.SimpleDateFormat;\nimport java.util.Base64;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.TimeZone;\n\n@Component\npublic class SharedVaultResponseMapper {\n\n    @Value(\"${kubernetes.jks.default-alias}\")\n    private String defaultAlias;\n    @Value(\"${kubernetes.jks.default-password}\")\n    private String defaultPassword;\n    @Value(\"${kubernetes.jks.default-secret-key-name}\")\n    private String defaultKeyName;\n\n    VaultSecret mapPki(VaultResponseData responseData) throws SecretNotAccessibleException {\n        try {\n            Certificate[] publicKeyList = getPublicKey(responseData.getCertificate());\n            X509Certificate compareCert = getCertificateWithShortestLivetime(publicKeyList);\n            SimpleDateFormat dateFormat = new SimpleDateFormat(Constants.DATE_FORMAT);\n            TimeZone tz = TimeZone.getTimeZone(\"UTC\");\n            dateFormat.setTimeZone(tz);\n            String compare = dateFormat.format(compareCert.getNotAfter());\n            Map<String, String> mappedPki = mappedCertValues(responseData);\n\n            return new VaultSecret(mappedPki, compare);\n        } catch (CertificateException e) {\n            throw new SecretNotAccessibleException(\"Couldn't get Expiration date of pki\", e);\n        }\n    }\n\n    VaultSecret mapCert(VaultResponseData vaultResponseData) {\n        Map<String, String> mappedPki = mappedCertValues(vaultResponseData);\n        String compareSha = Sha256.generateSha256(\n                mappedPki.get(\"tls.crt\"),\n                mappedPki.get(\"tls.key\")\n        );\n\n        return new VaultSecret(mappedPki, compareSha);\n    }\n\n    private Map<String, String> mappedCertValues(VaultResponseData vaultResponseData) {\n        String crt = getCrt(vaultResponseData);\n        String key = getKey(vaultResponseData);\n\n        Map<String, String> mappedPki = new HashMap<>();\n        mappedPki.put(\"tls.crt\", crt);\n        mappedPki.put(\"tls.key\", key);\n\n        return mappedPki;\n    }\n\n    private String getCrt(VaultResponseData responseData) {\n        return Base64.getEncoder().encodeToString(responseData.getChainedCertificate().getBytes());\n    }\n\n    private String getKey(VaultResponseData responseData) {\n        return Base64.getEncoder().encodeToString(responseData.getPrivate_key().getBytes());\n    }\n\n    VaultSecret mapJks(VaultResponseData data, VaultJKSConfiguration jksConfiguration, VaultType type) throws SecretNotAccessibleException {\n        try {\n            KeyStore keyStore = KeyStore.getInstance(\"PKCS12\");\n            keyStore.load(null, null);\n\n            Certificate[] publicKeyList = getPublicKey(data.getCertificate());\n\n            keyStore.setKeyEntry(\n                    getAlias(jksConfiguration),\n                    EncryptionUtils.loadPrivateKey(data.getPrivate_key()),\n                    getPassword(jksConfiguration).toCharArray(),\n                    publicKeyList);\n\n            if (jksConfiguration != null && StringUtils.hasText(jksConfiguration.getCaAlias())) {\n                keyStore.setCertificateEntry(\n                        jksConfiguration.getCaAlias(),\n                        getPublicKey(data.getIssuing_ca())[0]\n                );\n            }\n\n            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();\n            keyStore.store(outputStream, getPassword(jksConfiguration).toCharArray());\n            String b64KeyStore = Base64.getEncoder().encodeToString(outputStream.toByteArray());\n\n            HashMap<String, String> secretData = new HashMap<>() {{\n                put(getKey(jksConfiguration), b64KeyStore);\n            }};\n\n            String compare;\n            if (type.equals(VaultType.CERTJKS)) {\n                String base64Cert = Base64.getEncoder().encodeToString(data.getCertificate().getBytes());\n                String base64Key = Base64.getEncoder().encodeToString(data.getPrivate_key().getBytes());\n                compare = Sha256.generateSha256(base64Cert, base64Key);\n            } else {\n                // VaultType.PKIJKS\n                X509Certificate compareCert = getCertificateWithShortestLivetime(publicKeyList);\n                SimpleDateFormat dateFormat = new SimpleDateFormat(Constants.DATE_FORMAT);\n                TimeZone tz = TimeZone.getTimeZone(\"UTC\");\n                dateFormat.setTimeZone(tz);\n                compare = dateFormat.format(compareCert.getNotAfter());\n            }\n            return new VaultSecret(secretData, compare);\n\n\n        } catch (IOException | GeneralSecurityException e) {\n            throw new SecretNotAccessibleException(\"Couldn't generate keystore\", e);\n        }\n\n    }\n\n    private String getAlias(VaultJKSConfiguration jksConfiguration) {\n        if (jksConfiguration == null || !StringUtils.hasText(jksConfiguration.getAlias())) {\n            return defaultAlias;\n        }\n        return jksConfiguration.getAlias();\n    }\n\n    private String getPassword(VaultJKSConfiguration jksConfiguration) {\n        if (jksConfiguration == null || !StringUtils.hasText(jksConfiguration.getPassword())) {\n            return defaultPassword;\n        }\n        return jksConfiguration.getPassword();\n    }\n\n    private String getKey(VaultJKSConfiguration jksConfiguration) {\n        if (jksConfiguration == null || !StringUtils.hasText(jksConfiguration.getKeyName())) {\n            return defaultKeyName;\n        }\n        return jksConfiguration.getKeyName();\n    }\n\n    private Certificate[] getPublicKey(String pem) throws CertificateException {\n        return CertificateFactory.getInstance(\"X509\")\n                .generateCertificates(new ByteArrayInputStream(pem.getBytes())).toArray(new Certificate[0]);\n    }\n\n    private X509Certificate getCertificateWithShortestLivetime(Certificate[] certificates) {\n        if (certificates.length == 1) {\n            return (X509Certificate) certificates[0];\n        } else {\n            X509Certificate shortestLiveTime = (X509Certificate) certificates[0];\n\n            for (Certificate certificate : certificates) {\n                if (((X509Certificate) certificate).getNotAfter().before(shortestLiveTime.getNotAfter())) {\n                    shortestLiveTime = (X509Certificate) certificate;\n                }\n            }\n\n            return shortestLiveTime;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/impl/dockercfg/PullSecret.java",
    "content": "package de.koudingspawn.vault.vault.impl.dockercfg;\n\nimport java.util.Base64;\n\npublic class PullSecret {\n\n    private String username;\n    private String password;\n    private String email;\n    private String url;\n\n    public String getUsername() {\n        return username;\n    }\n\n    public void setUsername(String username) {\n        this.username = username;\n    }\n\n    public String getPassword() {\n        return password;\n    }\n\n    public void setPassword(String password) {\n        this.password = password;\n    }\n\n    public String getEmail() {\n        return email;\n    }\n\n    public void setEmail(String email) {\n        this.email = email;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    public void setUrl(String url) {\n        this.url = url;\n    }\n\n    public String getAuth() {\n        String concatedAuth = getUsername() + \":\" + getPassword();\n        return Base64.getEncoder().encodeToString(concatedAuth.getBytes());\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/impl/pki/PKIRequest.java",
    "content": "package de.koudingspawn.vault.vault.impl.pki;\n\npublic class PKIRequest {\n    private String common_name;\n    private String alt_names;\n    private String ip_sans;\n    private String format = \"pem\";\n    private String ttl;\n\n    public String getCommon_name() {\n        return common_name;\n    }\n\n    public void setCommon_name(String common_name) {\n        this.common_name = common_name;\n    }\n\n    public String getAlt_names() {\n        return alt_names;\n    }\n\n    public void setAlt_names(String alt_names) {\n        this.alt_names = alt_names;\n    }\n\n    public String getIp_sans() {\n        return ip_sans;\n    }\n\n    public void setIp_sans(String ip_sans) {\n        this.ip_sans = ip_sans;\n    }\n\n    public String getFormat() {\n        return format;\n    }\n\n    public void setFormat(String format) {\n        this.format = format;\n    }\n\n    public String getTtl() {\n        return ttl;\n    }\n\n    public void setTtl(String ttl) {\n        this.ttl = ttl;\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/impl/pki/PKIResponse.java",
    "content": "package de.koudingspawn.vault.vault.impl.pki;\n\npublic class PKIResponse {\n    private String lease_id;\n    private boolean renewable;\n    private VaultResponseData data;\n\n    public String getLease_id() {\n        return lease_id;\n    }\n\n    public void setLease_id(String lease_id) {\n        this.lease_id = lease_id;\n    }\n\n    public boolean isRenewable() {\n        return renewable;\n    }\n\n    public void setRenewable(boolean renewable) {\n        this.renewable = renewable;\n    }\n\n    public VaultResponseData getData() {\n        return data;\n    }\n\n    public void setData(VaultResponseData data) {\n        this.data = data;\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/impl/pki/VaultResponseData.java",
    "content": "package de.koudingspawn.vault.vault.impl.pki;\n\nimport org.springframework.util.CollectionUtils;\n\nimport java.util.List;\n\npublic class VaultResponseData {\n    private String certificate;\n    private String issuing_ca;\n    private List<String> ca_chain;\n    private String private_key;\n    private String private_key_type;\n    private String serial_number;\n\n    public String getCertificate() {\n        return certificate;\n    }\n\n    public void setCertificate(String certificate) {\n        this.certificate = certificate;\n    }\n\n    public String getIssuing_ca() {\n        return issuing_ca;\n    }\n\n    public void setIssuing_ca(String issuing_ca) {\n        this.issuing_ca = issuing_ca;\n    }\n\n    public List<String> getCa_chain() {\n        return ca_chain;\n    }\n\n    public void setCa_chain(List<String> ca_chain) {\n        this.ca_chain = ca_chain;\n    }\n\n    public String getPrivate_key() {\n        return private_key;\n    }\n\n    public void setPrivate_key(String private_key) {\n        this.private_key = private_key;\n    }\n\n    public String getPrivate_key_type() {\n        return private_key_type;\n    }\n\n    public void setPrivate_key_type(String private_key_type) {\n        this.private_key_type = private_key_type;\n    }\n\n    public String getSerial_number() {\n        return serial_number;\n    }\n\n    public void setSerial_number(String serial_number) {\n        this.serial_number = serial_number;\n    }\n\n    public String getChainedCertificate() {\n        StringBuilder sb = new StringBuilder(certificate);\n\n        if (!CollectionUtils.isEmpty(ca_chain)) {\n            ca_chain.forEach(cert -> sb.append(\"\\n\").append(cert));\n        }\n\n        return sb.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/de/koudingspawn/vault/vault/impl/properties/VaultJinjaLookup.java",
    "content": "package de.koudingspawn.vault.vault.impl.properties;\n\nimport de.koudingspawn.vault.vault.VaultCommunication;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\n\nimport java.util.HashMap;\nimport java.util.Optional;\n\npublic class VaultJinjaLookup {\n\n    private final VaultCommunication vaultCommunication;\n\n    public VaultJinjaLookup(VaultCommunication vaultCommunication) {\n        this.vaultCommunication = vaultCommunication;\n    }\n\n    public String lookup(String path, String key) throws SecretNotAccessibleException {\n        return vaultCommunication.getKeyValue(path).get(key).toString();\n    }\n\n    public HashMap lookup(String path) throws SecretNotAccessibleException {\n        return vaultCommunication.getKeyValue(path);\n    }\n\n    public HashMap lookupV2(String path) throws SecretNotAccessibleException {\n        return vaultCommunication.getVersionedSecret(path, Optional.empty());\n    }\n\n    public String lookupV2(String path, String key) throws SecretNotAccessibleException {\n        HashMap versionedSecret = vaultCommunication.getVersionedSecret(path, Optional.empty());\n        if (versionedSecret.containsKey(key)) {\n            return versionedSecret.get(key).toString();\n        }\n\n        throw new SecretNotAccessibleException(String.format(\"Secret at path %s with key %s not available\", path, key));\n    }\n\n    public String lookupV2(String path, int version, String key) throws SecretNotAccessibleException {\n        HashMap versionedSecret = vaultCommunication.getVersionedSecret(path, Optional.of(version));\n        if (versionedSecret.containsKey(key)) {\n            return versionedSecret.get(key).toString();\n        }\n\n        throw new SecretNotAccessibleException(String.format(\"Secret at path %s in version %d with key %s not available\", path, version, key));\n    }\n\n}\n"
  },
  {
    "path": "src/main/resources/application.properties",
    "content": "kubernetes.crd.group=koudingspawn.de\nkubernetes.crd.name=vault.koudingspawn.de\nkubernetes.vault.auth=token\nkubernetes.vault.token=root\nkubernetes.vault.role=admin\nkubernetes.vault.url=http://localhost:8200/v1/\nkubernetes.interval=60\nkubernetes.jks.default-alias=main\nkubernetes.jks.default-password=changeit\nkubernetes.jks.default-secret-key-name=key.jks\nkubernetes.ownerreference-fix.enabled=false\n\nmanagement.endpoints.enabled-by-default=false\nmanagement.endpoint.health.enabled=true\n"
  },
  {
    "path": "src/test/java/de/koudingspawn/vault/CertChainTest.java",
    "content": "package de.koudingspawn.vault;\n\nimport com.github.tomakehurst.wiremock.client.WireMock;\nimport com.github.tomakehurst.wiremock.junit.WireMockClassRule;\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.crd.VaultSpec;\nimport de.koudingspawn.vault.crd.VaultType;\nimport de.koudingspawn.vault.kubernetes.EventHandler;\nimport de.koudingspawn.vault.kubernetes.scheduler.impl.CertRefresh;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport io.fabric8.kubernetes.api.model.ObjectMetaBuilder;\nimport io.fabric8.kubernetes.api.model.Secret;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.KubernetesClientBuilder;\nimport org.junit.Before;\nimport org.junit.ClassRule;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport java.util.UUID;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.*;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED;\nimport static de.koudingspawn.vault.Constants.COMPARE_ANNOTATION;\nimport static de.koudingspawn.vault.Constants.LAST_UPDATE_ANNOTATION;\nimport static org.junit.Assert.*;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest(\n        properties = {\n                \"kubernetes.vault.url=http://localhost:8206/v1/\",\n                \"kubernetes.vault.token=c73ab0cb-41e6-b89c-7af6-96b36f1ac87b\"\n        }\n)\npublic class CertChainTest {\n\n    @ClassRule\n    public static final WireMockClassRule wireMockClassRule =\n            new WireMockClassRule(wireMockConfig().port(8206));\n\n    @Rule\n    public WireMockClassRule instanceRule = wireMockClassRule;\n\n    @Autowired\n    public EventHandler handler;\n\n    @Autowired\n    public KubernetesClient client;\n\n    @org.springframework.boot.test.context.TestConfiguration\n    static class KindConfig {\n\n        @Bean\n        @Primary\n        public KubernetesClient client() {\n            return new KubernetesClientBuilder().build();\n        }\n\n    }\n\n    @Autowired\n    CertRefresh certRefresh;\n\n    @Before\n    public void before() {\n        WireMock.resetAllScenarios();\n        client.secrets().inAnyNamespace().delete();\n\n        TestHelper.generateLookupSelfStub();\n    }\n\n    @Test\n    public void shouldGenerateCertFromVaultResource() {\n        Vault vault = new Vault();\n        vault.setMetadata(\n                new ObjectMetaBuilder().withName(\"certificate-1\").withNamespace(\"default\").withUid(UUID.randomUUID().toString()).build()\n        );\n        VaultSpec spec = new VaultSpec();\n        spec.setType(VaultType.CERT);\n        spec.setPath(\"secret/certificate\");\n        vault.setSpec(spec);\n\n        stubFor(get(urlEqualTo(\"/v1/secret/certificate\"))\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"\"\"\n                                {\n                                  \"request_id\": \"6cc090a8-3821-8244-73e4-5ab62b605587\",\n                                  \"lease_id\": \"\",\n                                  \"renewable\": false,\n                                  \"lease_duration\": 2764800,\n                                  \"data\": {\n                                    \"data\": {\n                                      \"certificate\": \"CERTIFICATE\",\n                                      \"issuing_ca\": \"ISSUINGCA\",\n                                      \"ca_chain\": [\"ISSUINGCA\"],\n                                      \"private_key\": \"PRIVATEKEY\"\n                                    }\n                                  },\n                                  \"wrap_info\": null,\n                                  \"warnings\": null,\n                                  \"auth\": null\n                                }\"\"\")));\n\n        handler.addHandler(vault);\n\n        Secret secret = client.secrets().inNamespace(\"default\").withName(\"certificate-1\").get();\n\n        assertEquals(\"certificate-1\", secret.getMetadata().getName());\n        assertEquals(\"default\", secret.getMetadata().getNamespace());\n        assertEquals(\"Opaque\", secret.getType());\n        assertNotNull(secret.getMetadata().getAnnotations().get(\"vault.koudingspawn.de\" + LAST_UPDATE_ANNOTATION));\n        assertEquals(\"GwzyEg3PQ2uSYFL2U6i0X2RibVs9p5gvOoTdZVQdT6s=\", secret.getMetadata().getAnnotations().get(\"vault.koudingspawn.de\" + COMPARE_ANNOTATION));\n\n        String crtB64 = secret.getData().get(\"tls.crt\");\n        String crt = new String(java.util.Base64.getDecoder().decode(crtB64));\n        String keyB64 = secret.getData().get(\"tls.key\");\n        String key = new String(java.util.Base64.getDecoder().decode(keyB64));\n\n        assertEquals(\"CERTIFICATE\\nISSUINGCA\", crt);\n        assertEquals(\"PRIVATEKEY\", key);\n    }\n\n    @Test\n    public void shouldCheckIfCertificateHasChangedAndReturnFalse() throws SecretNotAccessibleException {\n        Vault vault = new Vault();\n        vault.setMetadata(\n                new ObjectMetaBuilder().withName(\"certificate-2\").withNamespace(\"default\").withUid(UUID.randomUUID().toString()).build()\n        );\n        VaultSpec spec = new VaultSpec();\n        spec.setType(VaultType.CERT);\n        spec.setPath(\"secret/certificate\");\n        vault.setSpec(spec);\n\n        stubFor(get(urlEqualTo(\"/v1/secret/certificate\"))\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"\"\"\n                                {\n                                  \"request_id\": \"6cc090a8-3821-8244-73e4-5ab62b605587\",\n                                  \"lease_id\": \"\",\n                                  \"renewable\": false,\n                                  \"lease_duration\": 2764800,\n                                  \"data\": {\n                                    \"data\": {\n                                      \"certificate\": \"CERTIFICATE\",\n                                      \"issuing_ca\": \"ISSUINGCA\",\n                                      \"ca_chain\": [\"ISSUINGCA\"],\n                                      \"private_key\": \"PRIVATEKEY\"\n                                    }\n                                  },\n                                  \"wrap_info\": null,\n                                  \"warnings\": null,\n                                  \"auth\": null\n                                }\"\"\")));\n\n        handler.addHandler(vault);\n\n        assertFalse(certRefresh.refreshIsNeeded(vault));\n    }\n\n    @Test\n    public void shouldCheckIfCertificateHasChangedAndReturnTrue() throws SecretNotAccessibleException {\n        Vault vault = new Vault();\n        vault.setMetadata(\n                new ObjectMetaBuilder().withName(\"certificate-3\").withNamespace(\"default\").withUid(UUID.randomUUID().toString()).build()\n        );\n        VaultSpec spec = new VaultSpec();\n        spec.setType(VaultType.CERT);\n        spec.setPath(\"secret/certificate\");\n        vault.setSpec(spec);\n\n        stubFor(get(urlEqualTo(\"/v1/secret/certificate\"))\n                .inScenario(\"Cert secret change\")\n                .whenScenarioStateIs(STARTED)\n                .willSetStateTo(\"Cert first request done\")\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"\"\"\n                                {\n                                  \"request_id\": \"6cc090a8-3821-8244-73e4-5ab62b605587\",\n                                  \"lease_id\": \"\",\n                                  \"renewable\": false,\n                                  \"lease_duration\": 2764800,\n                                  \"data\": {\n                                    \"data\": {\n                                      \"certificate\": \"CERTIFICATE\",\n                                      \"issuing_ca\": \"ISSUINGCA\",\n                                      \"ca_chain\": [\"ISSUINGCA\"],\n                                      \"private_key\": \"PRIVATEKEY\"\n                                    }\n                                  },\n                                  \"wrap_info\": null,\n                                  \"warnings\": null,\n                                  \"auth\": null\n                                }\"\"\")));\n\n        stubFor(get(urlEqualTo(\"/v1/secret/certificate\"))\n                .inScenario(\"Cert secret change\")\n                .whenScenarioStateIs(\"Cert first request done\")\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"\"\"\n                                {\n                                  \"request_id\": \"6cc090a8-3821-8244-73e4-5ab62b605587\",\n                                  \"lease_id\": \"\",\n                                  \"renewable\": false,\n                                  \"lease_duration\": 2764800,\n                                  \"data\": {\n                                    \"data\": {\n                                      \"certificate\": \"CERTIFICATECHANGE\",\n                                      \"issuing_ca\": \"ISSUINGCA\",\n                                      \"ca_chain\": [\"ISSUINGCA\"],\n                                      \"private_key\": \"PRIVATEKEY\"\n                                    }\n                                  },\n                                  \"wrap_info\": null,\n                                  \"warnings\": null,\n                                  \"auth\": null\n                                }\"\"\")));\n\n        handler.addHandler(vault);\n\n        assertTrue(certRefresh.refreshIsNeeded(vault));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/de/koudingspawn/vault/CertTest.java",
    "content": "package de.koudingspawn.vault;\n\nimport com.github.tomakehurst.wiremock.client.WireMock;\nimport com.github.tomakehurst.wiremock.junit.WireMockClassRule;\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.crd.VaultSpec;\nimport de.koudingspawn.vault.crd.VaultType;\nimport de.koudingspawn.vault.kubernetes.EventHandler;\nimport de.koudingspawn.vault.kubernetes.scheduler.impl.CertRefresh;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport io.fabric8.kubernetes.api.model.ObjectMetaBuilder;\nimport io.fabric8.kubernetes.api.model.Secret;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.KubernetesClientBuilder;\nimport org.junit.Before;\nimport org.junit.ClassRule;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport java.util.UUID;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.*;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED;\nimport static de.koudingspawn.vault.Constants.COMPARE_ANNOTATION;\nimport static de.koudingspawn.vault.Constants.LAST_UPDATE_ANNOTATION;\nimport static org.junit.Assert.*;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest(\n        properties = {\n                \"kubernetes.vault.url=http://localhost:8201/v1/\"\n        }\n\n)\npublic class CertTest {\n\n    @ClassRule\n    public static final WireMockClassRule wireMockClassRule =\n            new WireMockClassRule(wireMockConfig().port(8201));\n\n    @Rule\n    public WireMockClassRule instanceRule = wireMockClassRule;\n\n    @Autowired\n    public EventHandler handler;\n\n    @Autowired\n    public KubernetesClient client;\n\n    @org.springframework.boot.test.context.TestConfiguration\n    static class KindConfig {\n\n        @Bean\n        @Primary\n        public KubernetesClient client() {\n            return new KubernetesClientBuilder().build();\n        }\n\n    }\n\n    @Autowired\n    CertRefresh certRefresh;\n\n    @Before\n    public void before() {\n        WireMock.resetAllScenarios();\n        client.secrets().inAnyNamespace().delete();\n\n        TestHelper.generateLookupSelfStub();\n    }\n\n    @Test\n    public void shouldGenerateCertFromVaultResource() {\n        Vault vault = new Vault();\n        vault.setMetadata(\n                new ObjectMetaBuilder().withName(\"certificate-1\").withNamespace(\"default\").withUid(UUID.randomUUID().toString()).build()\n        );\n        VaultSpec spec = new VaultSpec();\n        spec.setType(VaultType.CERT);\n        spec.setPath(\"secret/certificate\");\n        vault.setSpec(spec);\n\n        stubFor(get(urlEqualTo(\"/v1/secret/certificate\"))\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"\"\"\n                                {\n                                  \"request_id\": \"6cc090a8-3821-8244-73e4-5ab62b605587\",\n                                  \"lease_id\": \"\",\n                                  \"renewable\": false,\n                                  \"lease_duration\": 2764800,\n                                  \"data\": {\n                                    \"data\": {\n                                      \"certificate\": \"CERTIFICATE\",\n                                      \"issuing_ca\": \"ISSUINGCA\",\n                                      \"private_key\": \"PRIVATEKEY\"\n                                    }\n                                  },\n                                  \"wrap_info\": null,\n                                  \"warnings\": null,\n                                  \"auth\": null\n                                }\"\"\")));\n\n        handler.addHandler(vault);\n\n        Secret secret = client.secrets().inNamespace(\"default\").withName(\"certificate-1\").get();\n\n        assertEquals(\"certificate-1\", secret.getMetadata().getName());\n        assertEquals(\"default\", secret.getMetadata().getNamespace());\n        assertEquals(\"Opaque\", secret.getType());\n        assertNotNull(secret.getMetadata().getAnnotations().get(\"vault.koudingspawn.de\" + LAST_UPDATE_ANNOTATION));\n        assertEquals(\"NNreOhDpdqcmcxEvF/KGNSQBZpAjszzrhjQVT4X8EXE=\", secret.getMetadata().getAnnotations().get(\"vault.koudingspawn.de\" + COMPARE_ANNOTATION));\n\n        String crtB64 = secret.getData().get(\"tls.crt\");\n        String crt = new String(java.util.Base64.getDecoder().decode(crtB64));\n        String keyB64 = secret.getData().get(\"tls.key\");\n        String key = new String(java.util.Base64.getDecoder().decode(keyB64));\n\n        assertEquals(\"CERTIFICATE\", crt);\n        assertEquals(\"PRIVATEKEY\", key);\n    }\n\n    @Test\n    public void shouldCheckIfCertificateHasChangedAndReturnFalse() throws SecretNotAccessibleException {\n        Vault vault = new Vault();\n        vault.setMetadata(\n                new ObjectMetaBuilder().withName(\"certificate-2\").withNamespace(\"default\").withUid(UUID.randomUUID().toString()).build()\n        );\n        VaultSpec spec = new VaultSpec();\n        spec.setType(VaultType.CERT);\n        spec.setPath(\"secret/certificate\");\n        vault.setSpec(spec);\n\n        stubFor(get(urlEqualTo(\"/v1/secret/certificate\"))\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"\"\"\n                                {\n                                  \"request_id\": \"6cc090a8-3821-8244-73e4-5ab62b605587\",\n                                  \"lease_id\": \"\",\n                                  \"renewable\": false,\n                                  \"lease_duration\": 2764800,\n                                  \"data\": {\n                                    \"data\": {\n                                      \"certificate\": \"CERTIFICATE\",\n                                      \"issuing_ca\": \"ISSUINGCA\",\n                                      \"private_key\": \"PRIVATEKEY\"\n                                    }\n                                  },\n                                  \"wrap_info\": null,\n                                  \"warnings\": null,\n                                  \"auth\": null\n                                }\"\"\")));\n\n        handler.addHandler(vault);\n\n        assertFalse(certRefresh.refreshIsNeeded(vault));\n    }\n\n    @Test\n    public void shouldCheckIfCertificateHasChangedAndReturnTrue() throws SecretNotAccessibleException {\n        Vault vault = new Vault();\n        vault.setMetadata(\n                new ObjectMetaBuilder().withName(\"certificate-3\").withNamespace(\"default\").withUid(UUID.randomUUID().toString()).build()\n        );\n        VaultSpec spec = new VaultSpec();\n        spec.setType(VaultType.CERT);\n        spec.setPath(\"secret/certificate\");\n        vault.setSpec(spec);\n\n        stubFor(get(urlEqualTo(\"/v1/secret/certificate\"))\n                .inScenario(\"Cert secret change\")\n                .whenScenarioStateIs(STARTED)\n                .willSetStateTo(\"Cert first request done\")\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"\"\"\n                                {\n                                  \"request_id\": \"6cc090a8-3821-8244-73e4-5ab62b605587\",\n                                  \"lease_id\": \"\",\n                                  \"renewable\": false,\n                                  \"lease_duration\": 2764800,\n                                  \"data\": {\n                                    \"data\": {\n                                      \"certificate\": \"CERTIFICATE\",\n                                      \"issuing_ca\": \"ISSUINGCA\",\n                                      \"private_key\": \"PRIVATEKEY\"\n                                    }\n                                  },\n                                  \"wrap_info\": null,\n                                  \"warnings\": null,\n                                  \"auth\": null\n                                }\"\"\")));\n\n        stubFor(get(urlEqualTo(\"/v1/secret/certificate\"))\n                .inScenario(\"Cert secret change\")\n                .whenScenarioStateIs(\"Cert first request done\")\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"\"\"\n                                {\n                                  \"request_id\": \"6cc090a8-3821-8244-73e4-5ab62b605587\",\n                                  \"lease_id\": \"\",\n                                  \"renewable\": false,\n                                  \"lease_duration\": 2764800,\n                                  \"data\": {\n                                    \"data\": {\n                                      \"certificate\": \"CERTIFICATECHANGE\",\n                                      \"issuing_ca\": \"ISSUINGCA\",\n                                      \"private_key\": \"PRIVATEKEY\"\n                                    }\n                                  },\n                                  \"wrap_info\": null,\n                                  \"warnings\": null,\n                                  \"auth\": null\n                                }\"\"\")));\n\n        handler.addHandler(vault);\n\n        assertTrue(certRefresh.refreshIsNeeded(vault));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/de/koudingspawn/vault/DockerCfgTest.java",
    "content": "package de.koudingspawn.vault;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.github.tomakehurst.wiremock.client.WireMock;\nimport com.github.tomakehurst.wiremock.junit.WireMockClassRule;\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.crd.VaultDockerCfgConfiguration;\nimport de.koudingspawn.vault.crd.VaultSpec;\nimport de.koudingspawn.vault.crd.VaultType;\nimport de.koudingspawn.vault.kubernetes.EventHandler;\nimport de.koudingspawn.vault.kubernetes.scheduler.impl.DockerCfgRefresh;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport io.fabric8.kubernetes.api.model.DeletionPropagation;\nimport io.fabric8.kubernetes.api.model.ObjectMetaBuilder;\nimport io.fabric8.kubernetes.api.model.Secret;\nimport io.fabric8.kubernetes.client.DefaultKubernetesClient;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.KubernetesClientBuilder;\nimport org.junit.*;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport java.io.IOException;\nimport java.util.Base64;\nimport java.util.UUID;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.*;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED;\nimport static de.koudingspawn.vault.Constants.COMPARE_ANNOTATION;\nimport static de.koudingspawn.vault.Constants.LAST_UPDATE_ANNOTATION;\nimport static org.junit.Assert.*;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest(\n        properties = {\n                \"kubernetes.vault.url=http://localhost:8202/v1/\"\n        }\n\n)\npublic class DockerCfgTest {\n\n    @ClassRule\n    public static final WireMockClassRule wireMockClassRule =\n            new WireMockClassRule(wireMockConfig().port(8202));\n\n    @Rule\n    public WireMockClassRule instanceRule = wireMockClassRule;\n\n    @Autowired\n    public EventHandler handler;\n\n    @Autowired\n    public DockerCfgRefresh dockerCfgRefresh;\n\n    @Autowired\n    public KubernetesClient client;\n\n    @org.springframework.boot.test.context.TestConfiguration\n    static class KindConfig {\n\n        @Bean\n        @Primary\n        public KubernetesClient client() {\n            return new KubernetesClientBuilder().build();\n        }\n\n    }\n\n    @Before\n    public void before() {\n        WireMock.resetAllScenarios();\n        client.secrets().inAnyNamespace().delete();\n\n        TestHelper.generateLookupSelfStub();\n    }\n\n    @Test\n    public void shouldGenerateDockerCfgFromVaultResource() throws IOException {\n        Vault vault = new Vault();\n        vault.setMetadata(\n            new ObjectMetaBuilder().withName(\"dockercfg\").withNamespace(\"default\").withUid(UUID.randomUUID().toString()).build()\n        );\n        VaultSpec spec = new VaultSpec();\n        spec.setType(VaultType.DOCKERCFG);\n        spec.setPath(\"secret/docker\");\n        vault.setSpec(spec);\n\n        stubFor(get(urlEqualTo(\"/v1/secret/docker\"))\n                .inScenario(\"Simple Vault request\")\n                .whenScenarioStateIs(STARTED)\n            .willReturn(aResponse()\n                .withStatus(200)\n                .withHeader(\"Content-Type\", \"application/json\")\n                .withBody(\"\"\"\n                        {\n                          \"request_id\": \"6cc090a8-3821-8244-73e4-5ab62b605587\",\n                          \"lease_id\": \"\",\n                          \"renewable\": false,\n                          \"lease_duration\": 2764800,\n                          \"data\": {\n                            \"username\": \"username\",\n                            \"password\": \"password\",\n                            \"url\": \"hub.docker.com\",\n                            \"email\": \"test-user@test.com\"\n                          },\n                          \"wrap_info\": null,\n                          \"warnings\": null,\n                          \"auth\": null\n                        }\"\"\")));\n\n        handler.addHandler(vault);\n\n        Secret secret = client.secrets().inNamespace(\"default\").withName(\"dockercfg\").get();\n\n        assertEquals(\"dockercfg\", secret.getMetadata().getName());\n        assertEquals(\"default\", secret.getMetadata().getNamespace());\n        assertEquals(\"kubernetes.io/dockercfg\", secret.getType());\n        assertNotNull(secret.getMetadata().getAnnotations().get(\"vault.koudingspawn.de\" + LAST_UPDATE_ANNOTATION));\n        assertEquals(\"+gE+L0DNsGWDlNz5T3jLp1/U08KbD4OF+ez2lXQlTPM=\", secret.getMetadata().getAnnotations().get(\"vault.koudingspawn.de\" + COMPARE_ANNOTATION));\n\n        String dockerCfgBase64 = secret.getData().get(\".dockercfg\");\n        String dockerCfg = new String(Base64.getDecoder().decode(dockerCfgBase64));\n        ObjectMapper mapper = new ObjectMapper();\n        JsonNode dockerCfgNode = mapper.readTree(dockerCfg);\n        assertTrue(dockerCfgNode.has(\"hub.docker.com\"));\n\n        JsonNode credentials = dockerCfgNode.get(\"hub.docker.com\");\n        assertEquals(\"username\", credentials.get(\"username\").asText());\n        assertEquals(\"password\", credentials.get(\"password\").asText());\n        assertEquals(\"test-user@test.com\", credentials.get(\"email\").asText());\n        assertEquals(\"username:password\", new String(Base64.getDecoder().decode(credentials.get(\"auth\").asText())));\n    }\n\n    @Test\n    public void shouldCheckIfDockerCfgHasChangedAndReturnTrue() throws SecretNotAccessibleException {\n        Vault vault = new Vault();\n        vault.setMetadata(\n                new ObjectMetaBuilder().withName(\"dockercfg\").withNamespace(\"default\").withUid(UUID.randomUUID().toString()).build()\n        );\n        VaultSpec spec = new VaultSpec();\n        spec.setType(VaultType.DOCKERCFG);\n        spec.setPath(\"secret/docker\");\n        vault.setSpec(spec);\n\n        stubFor(get(urlEqualTo(\"/v1/secret/docker\"))\n                .inScenario(\"Docker secret change\")\n                .whenScenarioStateIs(STARTED)\n                .willSetStateTo(\"Docker first request done\")\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"{\\\"request_id\\\":\\\"6cc090a8-3821-8244-73e4-5ab62b605587\\\",\\\"lease_id\\\":\\\"\\\",\\\"renewable\\\":false,\\\"lease_duration\\\":2764800,\\\"data\\\":{\\\"username\\\":\\\"username\\\", \\\"password\\\": \\\"password\\\", \\\"url\\\": \\\"hub.docker.com\\\", \\\"email\\\": \\\"test-user@test.com\\\"},\\\"wrap_info\\\":null,\\\"warnings\\\":null,\\\"auth\\\":null}\")));\n\n        stubFor(get(urlEqualTo(\"/v1/secret/docker\"))\n                .inScenario(\"Docker secret change\")\n                .whenScenarioStateIs(\"Docker first request done\")\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"{\\\"request_id\\\":\\\"6cc090a8-3821-8244-73e4-5ab62b605587\\\",\\\"lease_id\\\":\\\"\\\",\\\"renewable\\\":false,\\\"lease_duration\\\":2764800,\\\"data\\\":{\\\"username\\\":\\\"usernamehaschanged\\\", \\\"password\\\": \\\"password\\\", \\\"url\\\": \\\"hub.docker.com\\\", \\\"email\\\": \\\"test-user@test.com\\\"},\\\"wrap_info\\\":null,\\\"warnings\\\":null,\\\"auth\\\":null}\")));\n\n        handler.addHandler(vault);\n        assertTrue(dockerCfgRefresh.refreshIsNeeded(vault));\n    }\n\n    @Test\n    public void shouldCheckIfDockerCfgHasChangedAndReturnFalse() throws SecretNotAccessibleException {\n        Vault vault = new Vault();\n        vault.setMetadata(\n                new ObjectMetaBuilder().withName(\"dockercfg\").withNamespace(\"default\").withUid(UUID.randomUUID().toString()).build()\n        );\n        VaultSpec spec = new VaultSpec();\n        spec.setType(VaultType.DOCKERCFG);\n        spec.setPath(\"secret/docker\");\n        vault.setSpec(spec);\n\n        stubFor(get(urlEqualTo(\"/v1/secret/docker\"))\n                .inScenario(\"Simple Vault request\")\n                .whenScenarioStateIs(STARTED)\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"{\\\"request_id\\\":\\\"6cc090a8-3821-8244-73e4-5ab62b605587\\\",\\\"lease_id\\\":\\\"\\\",\\\"renewable\\\":false,\\\"lease_duration\\\":2764800,\\\"data\\\":{\\\"username\\\":\\\"username\\\", \\\"password\\\": \\\"password\\\", \\\"url\\\": \\\"hub.docker.com\\\", \\\"email\\\": \\\"test-user@test.com\\\"},\\\"wrap_info\\\":null,\\\"warnings\\\":null,\\\"auth\\\":null}\")));\n\n        handler.addHandler(vault);\n        assertFalse(dockerCfgRefresh.refreshIsNeeded(vault));\n    }\n\n    @Test\n    public void shouldGenerateDockerCfgV2() throws JsonProcessingException {\n        Vault vault = new Vault();\n        vault.setMetadata(\n                new ObjectMetaBuilder().withName(\"dockercfg\").withNamespace(\"default\").withUid(UUID.randomUUID().toString()).build()\n        );\n        VaultSpec spec = new VaultSpec();\n        spec.setType(VaultType.DOCKERCFG);\n        spec.setPath(\"secret/docker\");\n\n        VaultDockerCfgConfiguration dockerConfig = new VaultDockerCfgConfiguration();\n        dockerConfig.setType(VaultType.KEYVALUEV2);\n        spec.setDockerCfgConfiguration(dockerConfig);\n        vault.setSpec(spec);\n\n        stubFor(get(urlPathMatching(\"/v1/secret/data/docker\"))\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"\"\"\n                                {\n                                  \"request_id\": \"1cfee2a6-318a-ea12-f5b5-6fd52d74d2c6\",\n                                  \"lease_id\": \"\",\n                                  \"renewable\": false,\n                                  \"lease_duration\": 0,\n                                  \"data\": {\n                                    \"data\": {\n                                      \"username\": \"username\",\n                                      \"password\": \"password\",\n                                      \"url\": \"hub.docker.com\",\n                                      \"email\": \"test-user@test.com\"\n                                    },\n                                    \"metadata\": {\n                                      \"created_time\": \"2018-12-10T18:59:53.337997525Z\",\n                                      \"deletion_time\": \"\",\n                                      \"destroyed\": false,\n                                      \"version\": 1\n                                    }\n                                  },\n                                  \"wrap_info\": null,\n                                  \"warnings\": null,\n                                  \"auth\": null\n                                }\"\"\")));\n\n        handler.addHandler(vault);\n\n        Secret secret = client.secrets().inNamespace(\"default\").withName(\"dockercfg\").get();\n        assertEquals(\"dockercfg\", secret.getMetadata().getName());\n        assertEquals(\"default\", secret.getMetadata().getNamespace());\n        assertEquals(\"kubernetes.io/dockercfg\", secret.getType());\n        assertNotNull(secret.getMetadata().getAnnotations().get(\"vault.koudingspawn.de\" + LAST_UPDATE_ANNOTATION));\n        assertEquals(\"+gE+L0DNsGWDlNz5T3jLp1/U08KbD4OF+ez2lXQlTPM=\", secret.getMetadata().getAnnotations().get(\"vault.koudingspawn.de\" + COMPARE_ANNOTATION));\n\n        String dockerCfgBase64 = secret.getData().get(\".dockercfg\");\n        String dockerCfg = new String(Base64.getDecoder().decode(dockerCfgBase64));\n        ObjectMapper mapper = new ObjectMapper();\n        JsonNode dockerCfgNode = mapper.readTree(dockerCfg);\n        assertTrue(dockerCfgNode.has(\"hub.docker.com\"));\n\n        JsonNode credentials = dockerCfgNode.get(\"hub.docker.com\");\n        assertEquals(\"username\", credentials.get(\"username\").asText());\n        assertEquals(\"password\", credentials.get(\"password\").asText());\n        assertEquals(\"test-user@test.com\", credentials.get(\"email\").asText());\n        assertEquals(\"username:password\", new String(Base64.getDecoder().decode(credentials.get(\"auth\").asText())));\n    }\n\n    @After\n    @Before\n    public void cleanup() {\n        Secret secret = client.secrets().inNamespace(\"default\").withName(\"dockercfg\").get();\n        if (secret != null) {\n            client.secrets().inNamespace(\"default\").withName(\"dockercfg\").withPropagationPolicy(DeletionPropagation.BACKGROUND).delete();\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/de/koudingspawn/vault/EventNotificationTest.java",
    "content": "package de.koudingspawn.vault;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.kubernetes.event.EventNotification;\nimport de.koudingspawn.vault.kubernetes.event.EventType;\nimport io.fabric8.kubernetes.api.model.ObjectMetaBuilder;\nimport io.fabric8.kubernetes.client.DefaultKubernetesClient;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.KubernetesClientBuilder;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.test.context.ActiveProfiles;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport java.util.UUID;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest(properties = {\n        \"kubernetes.vault.url=http://localhost:8202/v1/\"\n})\n@AutoConfigureMockMvc\n@ActiveProfiles(\"test\")\npublic class EventNotificationTest {\n\n    @Autowired\n    public KubernetesClient client;\n\n    @Autowired\n    public EventNotification evtNotification;\n\n    @org.springframework.boot.test.context.TestConfiguration\n    static class KindConfig {\n\n        @Bean\n        @Primary\n        public KubernetesClient client() {\n            return new KubernetesClientBuilder().build();\n        }\n    }\n\n    @Test\n    public void shouldBeAbleToCreateEvent() {\n        String uuid = UUID.randomUUID().toString();\n\n        Vault vault = new Vault();\n        vault.setMetadata(new ObjectMetaBuilder()\n                .withName(\"test\")\n                .withNamespace(\"default\")\n                .withUid(uuid)\n                .build());\n\n        evtNotification.storeNewEvent(EventType.CREATION_SUCCESSFUL, \"Successfully created secret\", vault);\n\n        assertEquals(1, client.v1().events().inNamespace(\"default\").list().getItems()\n                .stream().filter(event -> event.getInvolvedObject().getName().equals(\"test\") && event.getInvolvedObject().getUid().equals(uuid)).count());\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/de/koudingspawn/vault/KeyValueTest.java",
    "content": "package de.koudingspawn.vault;\n\nimport com.github.tomakehurst.wiremock.client.WireMock;\nimport com.github.tomakehurst.wiremock.junit.WireMockClassRule;\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.crd.VaultSpec;\nimport de.koudingspawn.vault.crd.VaultType;\nimport de.koudingspawn.vault.kubernetes.EventHandler;\nimport de.koudingspawn.vault.kubernetes.scheduler.impl.KeyValueRefresh;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport io.fabric8.kubernetes.api.model.ObjectMetaBuilder;\nimport io.fabric8.kubernetes.api.model.Secret;\nimport io.fabric8.kubernetes.client.DefaultKubernetesClient;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.KubernetesClientBuilder;\nimport org.junit.Before;\nimport org.junit.ClassRule;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport java.util.UUID;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.*;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED;\nimport static de.koudingspawn.vault.Constants.COMPARE_ANNOTATION;\nimport static de.koudingspawn.vault.Constants.LAST_UPDATE_ANNOTATION;\nimport static org.junit.Assert.*;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest(\n        properties = {\n                \"kubernetes.vault.url=http://localhost:8209/v1/\"\n        }\n)\npublic class KeyValueTest {\n\n    @ClassRule\n    public static final WireMockClassRule wireMockClassRule =\n            new WireMockClassRule(wireMockConfig().port(8209));\n\n    @Rule\n    public WireMockClassRule instanceRule = wireMockClassRule;\n\n    @Autowired\n    public EventHandler handler;\n\n    @Autowired\n    public KeyValueRefresh keyValueRefresh;\n\n    @Autowired\n    public KubernetesClient client;\n\n    @org.springframework.boot.test.context.TestConfiguration\n    static class KindConfig {\n\n        @Bean\n        @Primary\n        public KubernetesClient client() {\n            return new KubernetesClientBuilder().build();\n        }\n\n    }\n\n    @Before\n    public void before() {\n        WireMock.resetAllScenarios();\n        client.secrets().inAnyNamespace().delete();\n\n        TestHelper.generateLookupSelfStub();\n    }\n\n    @Test\n    public void shouldGenerateSimpleSecretFromVaultCustomResource() {\n        Vault vault = new Vault();\n        vault.setMetadata(\n                new ObjectMetaBuilder().withName(\"simple-kv1-1\").withNamespace(\"default\").withUid(UUID.randomUUID().toString()).build());\n        VaultSpec vaultSpec = new VaultSpec();\n        vaultSpec.setType(VaultType.KEYVALUE);\n        vaultSpec.setPath(\"secret/simple\");\n        vault.setSpec(vaultSpec);\n\n        stubFor(get(urlPathMatching(\"/v1/secret/simple\"))\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"{\\\"request_id\\\":\\\"6cc090a8-3821-8244-73e4-5ab62b605587\\\",\\\"lease_id\\\":\\\"\\\",\\\"renewable\\\":false,\\\"lease_duration\\\":2764800,\\\"data\\\":{\\\"key\\\":\\\"value\\\"},\\\"wrap_info\\\":null,\\\"warnings\\\":null,\\\"auth\\\":null}\")));\n\n        handler.addHandler(vault);\n\n        Secret secret = client.secrets().inNamespace(\"default\").withName(\"simple-kv1-1\").get();\n        assertEquals(\"simple-kv1-1\", secret.getMetadata().getName());\n        assertEquals(\"default\", secret.getMetadata().getNamespace());\n        assertEquals(\"Opaque\", secret.getType());\n        assertEquals(\"dmFsdWU=\", secret.getData().get(\"key\"));\n        assertNotNull(secret.getMetadata().getAnnotations().get(\"vault.koudingspawn.de\" + LAST_UPDATE_ANNOTATION));\n        assertEquals(\"dYxf3NXqZ1l2d1YL1htbVBs6EUot33VjoBUUrBJg1eY=\", secret.getMetadata().getAnnotations().get(\"vault.koudingspawn.de\" + COMPARE_ANNOTATION));\n    }\n\n    @Test\n    public void shouldCheckIfSimpleSecretHasChangedAndReturnTrue() throws SecretNotAccessibleException {\n        Vault vault = new Vault();\n        vault.setMetadata(\n                new ObjectMetaBuilder().withName(\"simple-kv1-2\").withNamespace(\"default\").withUid(UUID.randomUUID().toString()).build());\n        VaultSpec vaultSpec = new VaultSpec();\n        vaultSpec.setType(VaultType.KEYVALUE);\n        vaultSpec.setPath(\"secret/simple\");\n        vault.setSpec(vaultSpec);\n\n        stubFor(get(urlPathMatching(\"/v1/secret/simple\"))\n                .inScenario(\"Vault secret change\")\n                .whenScenarioStateIs(STARTED)\n                .willSetStateTo(\"First request done\")\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"{\\\"request_id\\\":\\\"6cc090a8-3821-8244-73e4-5ab62b605587\\\",\\\"lease_id\\\":\\\"\\\",\\\"renewable\\\":false,\\\"lease_duration\\\":2764800,\\\"data\\\":{\\\"key\\\":\\\"value\\\"},\\\"wrap_info\\\":null,\\\"warnings\\\":null,\\\"auth\\\":null}\")));\n\n        stubFor(get(urlPathMatching(\"/v1/secret/simple\"))\n                .inScenario(\"Vault secret change\")\n                .whenScenarioStateIs(\"First request done\")\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"{\\\"request_id\\\":\\\"6cc090a8-3821-8244-73e4-5ab62b605587\\\",\\\"lease_id\\\":\\\"\\\",\\\"renewable\\\":false,\\\"lease_duration\\\":2764800,\\\"data\\\":{\\\"key\\\":\\\"value1\\\"},\\\"wrap_info\\\":null,\\\"warnings\\\":null,\\\"auth\\\":null}\")));\n\n        handler.addHandler(vault);\n\n        assertTrue(keyValueRefresh.refreshIsNeeded(vault));\n    }\n\n    @Test\n    public void shouldCheckIfSimpleSecretHasChangedAndReturnFalse() throws SecretNotAccessibleException {\n        Vault vault = new Vault();\n        vault.setMetadata(\n                new ObjectMetaBuilder().withName(\"simple-kv1-3\").withNamespace(\"default\").withUid(UUID.randomUUID().toString()).build());\n        VaultSpec vaultSpec = new VaultSpec();\n        vaultSpec.setType(VaultType.KEYVALUE);\n        vaultSpec.setPath(\"secret/simple\");\n        vault.setSpec(vaultSpec);\n\n        stubFor(get(urlPathMatching(\"/v1/secret/simple\"))\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"{\\\"request_id\\\":\\\"6cc090a8-3821-8244-73e4-5ab62b605587\\\",\\\"lease_id\\\":\\\"\\\",\\\"renewable\\\":false,\\\"lease_duration\\\":2764800,\\\"data\\\":{\\\"key\\\":\\\"value\\\"},\\\"wrap_info\\\":null,\\\"warnings\\\":null,\\\"auth\\\":null}\")));\n\n        handler.addHandler(vault);\n\n        assertFalse(keyValueRefresh.refreshIsNeeded(vault));\n    }\n\n    @Test\n    public void preventNullPointerExceptionWhenSecretDoesNotExist() {\n        Vault vault = new Vault();\n        vault.setMetadata(\n                new ObjectMetaBuilder().withName(\"simple-kv1-4\").withNamespace(\"default\").withUid(UUID.randomUUID().toString()).build());\n        VaultSpec vaultSpec = new VaultSpec();\n        vaultSpec.setType(VaultType.KEYVALUE);\n        vaultSpec.setPath(\"secret/simple\");\n        vault.setSpec(vaultSpec);\n\n        stubFor(get(urlPathMatching(\"/v1/secret/simple\"))\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"{\\\"request_id\\\":\\\"6cc090a8-3821-8244-73e4-5ab62b605587\\\",\\\"lease_id\\\":\\\"\\\",\\\"renewable\\\":false,\\\"lease_duration\\\":2764800,\\\"data\\\":{\\\"key\\\":\\\"value\\\"},\\\"wrap_info\\\":null,\\\"warnings\\\":null,\\\"auth\\\":null}\")));\n\n        handler.modifyHandler(vault);\n\n        Secret secret = client.secrets().inNamespace(\"default\").withName(\"simple-kv1-4\").get();\n        assertEquals(\"simple-kv1-4\", secret.getMetadata().getName());\n        assertEquals(\"default\", secret.getMetadata().getNamespace());\n        assertEquals(\"Opaque\", secret.getType());\n        assertEquals(\"dmFsdWU=\", secret.getData().get(\"key\"));\n        assertNotNull(secret.getMetadata().getAnnotations().get(\"vault.koudingspawn.de\" + LAST_UPDATE_ANNOTATION));\n        assertEquals(\"dYxf3NXqZ1l2d1YL1htbVBs6EUot33VjoBUUrBJg1eY=\", secret.getMetadata().getAnnotations().get(\"vault.koudingspawn.de\" + COMPARE_ANNOTATION));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/de/koudingspawn/vault/KeyValueV2Test.java",
    "content": "package de.koudingspawn.vault;\n\nimport com.github.tomakehurst.wiremock.client.WireMock;\nimport com.github.tomakehurst.wiremock.junit.WireMockClassRule;\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.crd.VaultSpec;\nimport de.koudingspawn.vault.crd.VaultType;\nimport de.koudingspawn.vault.kubernetes.EventHandler;\nimport de.koudingspawn.vault.kubernetes.scheduler.impl.KeyValueV2Refresh;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport io.fabric8.kubernetes.api.model.ObjectMetaBuilder;\nimport io.fabric8.kubernetes.api.model.Secret;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.KubernetesClientBuilder;\nimport org.junit.Before;\nimport org.junit.ClassRule;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.test.context.ActiveProfiles;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport java.util.UUID;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.*;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED;\nimport static de.koudingspawn.vault.Constants.COMPARE_ANNOTATION;\nimport static de.koudingspawn.vault.Constants.LAST_UPDATE_ANNOTATION;\nimport static org.junit.Assert.*;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest(\n        properties = {\n                \"kubernetes.vault.url=http://localhost:8207/v1/\"\n        }\n)\n@ActiveProfiles(\"test\")\npublic class KeyValueV2Test {\n\n    @ClassRule\n    public static final WireMockClassRule wireMockClassRule =\n            new WireMockClassRule(wireMockConfig().port(8207));\n\n    @Rule\n    public WireMockClassRule instanceRule = wireMockClassRule;\n\n    @Autowired\n    public EventHandler handler;\n\n    @Autowired\n    public KeyValueV2Refresh keyValueV2Refresh;\n\n    @Autowired\n    public KubernetesClient client;\n\n    @org.springframework.boot.test.context.TestConfiguration\n    static class KindConfig {\n\n        @Bean\n        @Primary\n        public KubernetesClient client() {\n            return new KubernetesClientBuilder().build();\n        }\n\n    }\n\n    @Before\n    public void before() {\n        WireMock.resetAllScenarios();\n        client.secrets().inAnyNamespace().delete();\n\n        TestHelper.generateLookupSelfStub();\n    }\n\n    @Test\n    public void shouldGenerateSimpleSecretFromVaultCustomResource() {\n        Vault vault = new Vault();\n        vault.setMetadata(\n                new ObjectMetaBuilder().withName(\"simple-kv2-1\").withNamespace(\"default\").withUid(UUID.randomUUID().toString()).build());\n        VaultSpec vaultSpec = new VaultSpec();\n        vaultSpec.setType(VaultType.KEYVALUEV2);\n        vaultSpec.setPath(\"secret/simple\");\n        vault.setSpec(vaultSpec);\n\n        stubFor(get(urlPathMatching(\"/v1/secret/data/simple\"))\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"\"\"\n                                {\n                                  \"request_id\": \"1cfee2a6-318a-ea12-f5b5-6fd52d74d2c6\",\n                                  \"lease_id\": \"\",\n                                  \"renewable\": false,\n                                  \"lease_duration\": 0,\n                                  \"data\": {\n                                    \"data\": {\n                                      \"key\": \"value\"\n                                    },\n                                    \"metadata\": {\n                                      \"created_time\": \"2018-12-10T18:59:53.337997525Z\",\n                                      \"deletion_time\": \"\",\n                                      \"destroyed\": false,\n                                      \"version\": 1\n                                    }\n                                  },\n                                  \"wrap_info\": null,\n                                  \"warnings\": null,\n                                  \"auth\": null\n                                }\"\"\")));\n\n        handler.addHandler(vault);\n\n        Secret secret = client.secrets().inNamespace(\"default\").withName(\"simple-kv2-1\").get();\n        assertEquals(\"simple-kv2-1\", secret.getMetadata().getName());\n        assertEquals(\"default\", secret.getMetadata().getNamespace());\n        assertEquals(\"Opaque\", secret.getType());\n        assertEquals(\"dmFsdWU=\", secret.getData().get(\"key\"));\n        assertNotNull(secret.getMetadata().getAnnotations().get(\"vault.koudingspawn.de\" + LAST_UPDATE_ANNOTATION));\n        assertEquals(\"dYxf3NXqZ1l2d1YL1htbVBs6EUot33VjoBUUrBJg1eY=\", secret.getMetadata().getAnnotations().get(\"vault.koudingspawn.de\" + COMPARE_ANNOTATION));\n    }\n\n    @Test\n    public void shouldCheckIfSimpleSecretHasChangedAndReturnTrue() throws SecretNotAccessibleException {\n        Vault vault = new Vault();\n        vault.setMetadata(\n                new ObjectMetaBuilder().withName(\"simple-kv2-2\").withNamespace(\"default\").withUid(UUID.randomUUID().toString()).build());\n        VaultSpec vaultSpec = new VaultSpec();\n        vaultSpec.setType(VaultType.KEYVALUEV2);\n        vaultSpec.setPath(\"secret/simple\");\n        vault.setSpec(vaultSpec);\n\n        stubFor(get(urlPathMatching(\"/v1/secret/data/simple\"))\n                .inScenario(\"Vault secret change\")\n                .whenScenarioStateIs(STARTED)\n                .willSetStateTo(\"First request done\")\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"\"\"\n                                {\n                                  \"request_id\": \"1cfee2a6-318a-ea12-f5b5-6fd52d74d2c6\",\n                                  \"lease_id\": \"\",\n                                  \"renewable\": false,\n                                  \"lease_duration\": 0,\n                                  \"data\": {\n                                    \"data\": {\n                                      \"key\": \"value\"\n                                    },\n                                    \"metadata\": {\n                                      \"created_time\": \"2018-12-10T18:59:53.337997525Z\",\n                                      \"deletion_time\": \"\",\n                                      \"destroyed\": false,\n                                      \"version\": 1\n                                    }\n                                  },\n                                  \"wrap_info\": null,\n                                  \"warnings\": null,\n                                  \"auth\": null\n                                }\"\"\")));\n\n        stubFor(get(urlPathMatching(\"/v1/secret/data/simple\"))\n                .inScenario(\"Vault secret change\")\n                .whenScenarioStateIs(\"First request done\")\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"\"\"\n                                {\n                                  \"request_id\": \"1cfee2a6-318a-ea12-f5b5-6fd52d74d2c6\",\n                                  \"lease_id\": \"\",\n                                  \"renewable\": false,\n                                  \"lease_duration\": 0,\n                                  \"data\": {\n                                    \"data\": {\n                                      \"key\": \"value1\"\n                                    },\n                                    \"metadata\": {\n                                      \"created_time\": \"2018-12-10T18:59:53.337997525Z\",\n                                      \"deletion_time\": \"\",\n                                      \"destroyed\": false,\n                                      \"version\": 1\n                                    }\n                                  },\n                                  \"wrap_info\": null,\n                                  \"warnings\": null,\n                                  \"auth\": null\n                                }\"\"\")));\n\n        handler.addHandler(vault);\n\n        assertTrue(keyValueV2Refresh.refreshIsNeeded(vault));\n    }\n\n    @Test\n    public void shouldCheckIfSimpleSecretHasChangedAndReturnFalse() throws SecretNotAccessibleException {\n        Vault vault = new Vault();\n        vault.setMetadata(\n                new ObjectMetaBuilder().withName(\"simple-kv2-3\").withNamespace(\"default\").withUid(UUID.randomUUID().toString()).build());\n        VaultSpec vaultSpec = new VaultSpec();\n        vaultSpec.setType(VaultType.KEYVALUEV2);\n        vaultSpec.setPath(\"secret/simple\");\n        vault.setSpec(vaultSpec);\n\n        stubFor(get(urlPathMatching(\"/v1/secret/data/simple\"))\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"\"\"\n                                {\n                                  \"request_id\": \"1cfee2a6-318a-ea12-f5b5-6fd52d74d2c6\",\n                                  \"lease_id\": \"\",\n                                  \"renewable\": false,\n                                  \"lease_duration\": 0,\n                                  \"data\": {\n                                    \"data\": {\n                                      \"key\": \"value\"\n                                    },\n                                    \"metadata\": {\n                                      \"created_time\": \"2018-12-10T18:59:53.337997525Z\",\n                                      \"deletion_time\": \"\",\n                                      \"destroyed\": false,\n                                      \"version\": 1\n                                    }\n                                  },\n                                  \"wrap_info\": null,\n                                  \"warnings\": null,\n                                  \"auth\": null\n                                }\"\"\")));\n\n        handler.addHandler(vault);\n\n        assertFalse(keyValueV2Refresh.refreshIsNeeded(vault));\n    }\n\n    @Test\n    public void shouldSupportNestedPath() {\n        Vault vault = new Vault();\n        vault.setMetadata(\n                new ObjectMetaBuilder().withName(\"simple-kv2-4\").withNamespace(\"default\").withUid(UUID.randomUUID().toString()).build());\n        VaultSpec vaultSpec = new VaultSpec();\n        vaultSpec.setType(VaultType.KEYVALUEV2);\n        vaultSpec.setPath(\"secret/simple/nested\");\n        vault.setSpec(vaultSpec);\n\n        stubFor(get(urlPathMatching(\"/v1/secret/data/simple/nested\"))\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"\"\"\n                                {\n                                  \"request_id\": \"1cfee2a6-318a-ea12-f5b5-6fd52d74d2c6\",\n                                  \"lease_id\": \"\",\n                                  \"renewable\": false,\n                                  \"lease_duration\": 0,\n                                  \"data\": {\n                                    \"data\": {\n                                      \"key\": \"value\",\n                                      \"nested\": \"value2\"\n                                    },\n                                    \"metadata\": {\n                                      \"created_time\": \"2018-12-10T18:59:53.337997525Z\",\n                                      \"deletion_time\": \"\",\n                                      \"destroyed\": false,\n                                      \"version\": 1\n                                    }\n                                  },\n                                  \"wrap_info\": null,\n                                  \"warnings\": null,\n                                  \"auth\": null\n                                }\"\"\")));\n\n        handler.addHandler(vault);\n\n        Secret secret = client.secrets().inNamespace(\"default\").withName(\"simple-kv2-4\").get();\n        assertEquals(\"simple-kv2-4\", secret.getMetadata().getName());\n        assertEquals(\"default\", secret.getMetadata().getNamespace());\n        assertEquals(\"Opaque\", secret.getType());\n        assertEquals(\"dmFsdWU=\", secret.getData().get(\"key\"));\n        assertEquals(\"dmFsdWUy\", secret.getData().get(\"nested\"));\n        assertNotNull(secret.getMetadata().getAnnotations().get(\"vault.koudingspawn.de\" + LAST_UPDATE_ANNOTATION));\n        assertEquals(\"z/SCo8oELBDAF2DQvX2H3yLs6vvn55Z6c8fdS3Y7l64=\", secret.getMetadata().getAnnotations().get(\"vault.koudingspawn.de\" + COMPARE_ANNOTATION));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/de/koudingspawn/vault/OwnerReferenceBugfixTest.java",
    "content": "package de.koudingspawn.vault;\n\nimport com.github.tomakehurst.wiremock.client.WireMock;\nimport com.github.tomakehurst.wiremock.junit.WireMockClassRule;\nimport com.google.common.collect.ImmutableMap;\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.crd.VaultList;\nimport de.koudingspawn.vault.kubernetes.EventHandler;\nimport io.fabric8.kubernetes.api.model.ObjectMetaBuilder;\nimport io.fabric8.kubernetes.api.model.OwnerReference;\nimport io.fabric8.kubernetes.api.model.Secret;\nimport io.fabric8.kubernetes.api.model.SecretBuilder;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.KubernetesClientBuilder;\nimport io.fabric8.kubernetes.client.dsl.MixedOperation;\nimport io.fabric8.kubernetes.client.dsl.Resource;\nimport org.junit.Before;\nimport org.junit.ClassRule;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport java.io.IOException;\nimport java.util.Collections;\n\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static de.koudingspawn.vault.PropertiesTest.generatePropertiesManifest;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest(\n        properties = {\n                \"kubernetes.vault.url=http://localhost:8210/v1/\",\n                \"kubernetes.vault.token=c73ab0cb-41e6-b89c-7af6-96b36f1ac87b\"\n        }\n\n)\npublic class OwnerReferenceBugfixTest {\n\n    @ClassRule\n    public static final WireMockClassRule wireMockClassRule =\n            new WireMockClassRule(wireMockConfig().port(8210));\n\n    @Rule\n    public WireMockClassRule instanceRule = wireMockClassRule;\n\n    @Autowired\n    public EventHandler handler;\n\n    @Autowired\n    public KubernetesClient client;\n\n    @Autowired\n    public MixedOperation<Vault, VaultList, Resource<Vault>> customResource;\n\n    @org.springframework.boot.test.context.TestConfiguration\n    static class KindConfig {\n\n        @Bean\n        @Primary\n        public KubernetesClient client() {\n            return new KubernetesClientBuilder().build();\n        }\n\n    }\n\n    @Before\n    public void before() {\n        WireMock.resetAllScenarios();\n        client.secrets().inAnyNamespace().delete();\n\n        TestHelper.generateLookupSelfStub();\n    }\n\n    @Test\n    public void hasCorrectOwnerReference() throws IOException {\n        TestHelper.generateKVStup(\"kv/key\", ImmutableMap.of(\"value\", \"kv1content\"));\n        TestHelper.generateKV2Stup(\"kv2/key\", ImmutableMap.of(\"value\", \"kv2content\", \"value2\", \"kv3content\"));\n\n        Vault vault = generatePropertiesManifest(\"properties-correct-owner-1\");\n        handler.addHandler(vault);\n\n        Secret secret = client.secrets().inNamespace(\"default\").withName(\"properties-correct-owner-1\").get();\n        assertEquals(1, secret.getMetadata().getOwnerReferences().size());\n        assertEquals(\"something-not-garbage-collected.de/v1\", secret.getMetadata().getOwnerReferences().get(0).getApiVersion());\n    }\n\n    @Test\n    public void fixOwnerReference() throws IOException {\n        TestHelper.generateKVStup(\"kv/key\", ImmutableMap.of(\"value\", \"kv1content\"));\n        TestHelper.generateKV2Stup(\"kv2/key\", ImmutableMap.of(\"value\", \"kv2content\", \"value2\", \"kv3content\"));\n\n        Vault vault = generatePropertiesManifest(\"properties-correct-owner-2\");\n        Secret secret = new SecretBuilder()\n                .withMetadata(\n                        new ObjectMetaBuilder().withName(\"properties-correct-owner-2\").withNamespace(\"default\")\n                                .addToOwnerReferences(\n                                        new OwnerReference(\n                                                \"vault.koudingspawn.de/v1\",\n                                                false,\n                                                true,\n                                                \"Vault\",\n                                                \"properties-correct-owner-2\",\n                                                vault.getMetadata().getUid()\n                                        )\n                                ).build()\n                )\n                .withData(Collections.singletonMap(\"key\", \"dmFsdWU=\"))\n                .build();\n        client.secrets().inNamespace(\"default\").resource(secret).create();\n\n        handler.addHandler(vault);\n\n        Secret foundSecret = client.secrets().inNamespace(\"default\").withName(\"properties-correct-owner-2\").get();\n        assertNotNull(foundSecret);\n        assertEquals(1, foundSecret.getMetadata().getOwnerReferences().size());\n        assertEquals(\"something-not-garbage-collected.de/v1\", foundSecret.getMetadata().getOwnerReferences().get(0).getApiVersion());\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/de/koudingspawn/vault/PKIChainTest.java",
    "content": "package de.koudingspawn.vault;\n\nimport com.github.tomakehurst.wiremock.client.WireMock;\nimport com.github.tomakehurst.wiremock.junit.WireMockClassRule;\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.crd.VaultPkiConfiguration;\nimport de.koudingspawn.vault.crd.VaultSpec;\nimport de.koudingspawn.vault.crd.VaultType;\nimport de.koudingspawn.vault.kubernetes.EventHandler;\nimport de.koudingspawn.vault.kubernetes.scheduler.impl.CertRefresh;\nimport de.koudingspawn.vault.vault.impl.pki.VaultResponseData;\nimport io.fabric8.kubernetes.api.model.DeletionPropagation;\nimport io.fabric8.kubernetes.api.model.ObjectMetaBuilder;\nimport io.fabric8.kubernetes.api.model.Secret;\nimport io.fabric8.kubernetes.client.DefaultKubernetesClient;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.KubernetesClientBuilder;\nimport org.junit.*;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport java.text.ParseException;\nimport java.text.SimpleDateFormat;\nimport java.time.LocalDateTime;\nimport java.time.ZoneId;\nimport java.time.temporal.ChronoUnit;\nimport java.util.*;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.*;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static de.koudingspawn.vault.Constants.*;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest(\n        properties = {\n                \"kubernetes.vault.url=http://localhost:8205/v1/\"\n        }\n)\npublic class PKIChainTest {\n\n    @ClassRule\n    public static final WireMockClassRule wireMockClassRule =\n            new WireMockClassRule(wireMockConfig().port(8205));\n\n    @Rule\n    public WireMockClassRule instanceRule = wireMockClassRule;\n\n    @Autowired\n    public EventHandler handler;\n\n    @Autowired\n    public KubernetesClient client;\n\n    @org.springframework.boot.test.context.TestConfiguration\n    static class KindConfig {\n\n        @Bean\n        @Primary\n        public KubernetesClient client() {\n            return new KubernetesClientBuilder().build();\n        }\n\n    }\n\n    @Autowired\n    CertRefresh certRefresh;\n\n    @Before\n    public void before() {\n        WireMock.resetAllScenarios();\n        client.secrets().inAnyNamespace().delete();\n\n        TestHelper.generateLookupSelfStub();\n    }\n\n    @Test\n    public void shouldGeneratePkiFromVaultChainResource() throws Exception {\n        VaultResponseData keyPair = generateKeyPair();\n        Vault vaultResource = generateVaultResource();\n\n\n        stubFor(post(urlEqualTo(\"/v1/testpki/issue/testrole\"))\n                .withRequestBody(matchingJsonPath(\"$.common_name\", containing(\"test.url.de\")))\n                .withRequestBody(matchingJsonPath(\"$.ttl\", containing(\"10m\")))\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\n                                String.format(\"\"\"\n                                        {\n                                          \"request_id\": \"6cc090a8-3821-8244-73e4-5ab62b605587\",\n                                          \"lease_id\": \"\",\n                                          \"renewable\": false,\n                                          \"lease_duration\": 2764800,\n                                          \"data\": {\n                                              \"certificate\": \"%s\",\n                                              \"ca_chain\": [\"%s\"],\n                                              \"issuing_ca\": \"%s\",\n                                              \"private_key\": \"%s\"\n                                          },\n                                          \"wrap_info\": null,\n                                          \"warnings\": null,\n                                          \"auth\": null\n                                        }\"\"\", keyPair.getCertificate(), keyPair.getCa_chain().get(0), keyPair.getIssuing_ca(), keyPair.getPrivate_key())\n                        )));\n\n        handler.addHandler(vaultResource);\n\n        Secret secret = client.secrets().inNamespace(\"default\").withName(\"pki\").get();\n\n        // metadata\n        assertEquals(\"pki\", secret.getMetadata().getName());\n        assertEquals(\"default\", secret.getMetadata().getNamespace());\n        assertEquals(\"Opaque\", secret.getType());\n        assertNotNull(secret.getMetadata().getAnnotations().get(\"vault.koudingspawn.de\" + LAST_UPDATE_ANNOTATION));\n\n        // compare date\n        String formatedExpirationDate = secret.getMetadata().getAnnotations().get(\"vault.koudingspawn.de\" + COMPARE_ANNOTATION);\n        LocalDateTime parsedCompareDate = parseDate(formatedExpirationDate);\n        LocalDateTime expirationDate = parseDate(\"2018-06-25T16:02Z\");\n        assertEquals(expirationDate, parsedCompareDate);\n\n        // body\n        String crtB64 = secret.getData().get(\"tls.crt\");\n        String crt = new String(Base64.getDecoder().decode(crtB64));\n        String keyB64 = secret.getData().get(\"tls.key\");\n        String key = new String(Base64.getDecoder().decode(keyB64));\n\n        // not so nice, but wiremock expects double escaping\n        assertEquals(keyPair.getChainedCertificate().replaceAll(\"\\\\\\\\n\", \"\").replaceAll(\"\\\\n\", \"\"), crt.replaceAll(\"\\\\n\", \"\"));\n        assertEquals(keyPair.getPrivate_key().replaceAll(\"\\\\\\\\n\", \"\").replaceAll(\"\\\\n\", \"\"), key.replaceAll(\"\\\\n\", \"\"));\n    }\n\n    @After\n    @Before\n    public void cleanup() {\n        Secret secret = client.secrets().inNamespace(\"default\").withName(\"pki\").get();\n        if (secret != null) {\n            client.secrets().inNamespace(\"default\").withName(\"pki\").withPropagationPolicy(DeletionPropagation.BACKGROUND).delete();\n        }\n    }\n\n    private VaultResponseData generateKeyPair() {\n        String certificate = \"-----BEGIN CERTIFICATE-----\\\\n\" +\n                \"MIIDZjCCAk6gAwIBAgIUc8PIl50sEQM28x6CV7iK6fae4t4wDQYJKoZIhvcNAQEL\\\\n\" +\n                \"BQAwLTErMCkGA1UEAxMibXl2YXVsdC5jb20gSW50ZXJtZWRpYXRlIEF1dGhvcml0\\\\n\" +\n                \"eTAeFw0xODA2MjIxNjAyMDVaFw0xODA2MjUxNjAyMzRaMBsxGTAXBgNVBAMTEGJs\\\\n\" +\n                \"YWguZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCu\\\\n\" +\n                \"lPq1WzzgImZFQ3NNlu9i7Xi6/U1a40csFz9Gvho3lIMgY2IgtxwaZTTO5vplKr+s\\\\n\" +\n                \"VfM2f6Enxv89i5If6J1gE1R/X728XYqNeXAP/5jgRwaq9S7Eg1len5OgXdkjO3RV\\\\n\" +\n                \"WkY8zMmG8N6e0viNgs9cYm9bJV9u9bKDeXYRaDeiSVIh77dL6Vaws06ViJeDzQxp\\\\n\" +\n                \"kDiaeSY9jyjhwBor+nqw7Vrvqc8LjaKy5JzD9rUPcv7O0hy3HF0/D3s2ailNdLar\\\\n\" +\n                \"4U9qEViI/5BzsykcJnvaLFW3RqZ1DmlUUoompOMURFMwbrEI3Gu4rKXmlu5zc9dx\\\\n\" +\n                \"UiDjnTup7e0hK4mNhqZ/AgMBAAGjgY8wgYwwDgYDVR0PAQH/BAQDAgOoMB0GA1Ud\\\\n\" +\n                \"JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUEsdbwRqYE9nRbcJE\\\\n\" +\n                \"XnAZHjxnnxEwHwYDVR0jBBgwFoAUTu0jLnLe15sBQhrYSEbXAILoLkMwGwYDVR0R\\\\n\" +\n                \"BBQwEoIQYmxhaC5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAjyqdfK81\\\\n\" +\n                \"NjkwS4heUg3DQtSILurLyzt+x+yafPnJTByoX2UE+xAUBeKwyxUmcRO6/IEUdZmM\\\\n\" +\n                \"qmGE44x2gyP3+YfjBSkfjKRQz8IslZWW4DPn11O+icpQieNAhFt6goD8rReNeH+i\\\\n\" +\n                \"OWVr+vBwi71C7uR9W4NkBtdCqXBfXrSkwtb9aIFZxr+bfYTIFCfsFnv8OAYCbzhk\\\\n\" +\n                \"6QtrWjQKduyuxisuvVAztJhk0JMg09xYgTsCJ8oQBNAwYR5UOl55TADgj19R1Xpq\\\\n\" +\n                \"8qT7r56++C5I0BMCMk63Q1ofgeYyTGJYsxjjNa+rLLYlK9ysOofrrLYdyp03xniK\\\\n\" +\n                \"IX4NZ1EHqWONxQ==\\\\n\" +\n                \"-----END CERTIFICATE-----\";\n        String caChain = \"-----BEGIN CERTIFICATE-----\\\\n\" +\n                \"MIIDNDCCAhygAwIBAgIUOM/FWyCOxZuYgAanfIk+11NQHLswDQYJKoZIhvcNAQEL\\\\n\" +\n                \"BQAwFjEUMBIGA1UEAxMLbXl2YXVsdC5jb20wHhcNMTgwNjIyMTU1OTI3WhcNMTgw\\\\n\" +\n                \"NzI0MTU1OTU3WjAtMSswKQYDVQQDEyJteXZhdWx0LmNvbSBJbnRlcm1lZGlhdGUg\\\\n\" +\n                \"QXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt6EKCNbb\\\\n\" +\n                \"+02nY/jM8EIwJ8moLUo6hCqOsEb4jFWYLjbInKICqdO36KsUEQL9W9Kq6LGqdZV4\\\\n\" +\n                \"cOYbpWYXr20Ni3p7hSgIttX+LJVPnm9g4Yc/71Wtzv9YsFXsudwQXE+iG+eBH2V2\\\\n\" +\n                \"kHbqANh/8ZXDzhZUlNecgR44YOOmS8z0nh3fOYwBu4eTazBvRk9PaUqS6VPtgqNF\\\\n\" +\n                \"sUAa7rszmOTRxVVsAN+O/HS08/+vwkIgvgTV849Pvb6diBlBWBc1LOOVuV+UWEsl\\\\n\" +\n                \"jfmhCM/nHqtGxg/cnPEV35WjAZH+ND+nkC2wRKaxfxf3B03MUm6WwIrRZrhMsBt2\\\\n\" +\n                \"OeEabv+NxKydDwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw\\\\n\" +\n                \"AwEB/zAdBgNVHQ4EFgQUTu0jLnLe15sBQhrYSEbXAILoLkMwHwYDVR0jBBgwFoAU\\\\n\" +\n                \"LzwRuDNnbxWfzW/iiM+Ek963I28wDQYJKoZIhvcNAQELBQADggEBADgwe8v8YPkJ\\\\n\" +\n                \"8Rsx9VkACu6IZ8hDkhDEe82wtU9BdzyIahgPgSwbjoLSIxX9nN3b8ifX1ZNgeio7\\\\n\" +\n                \"hkCQ8q0s3Eor479IqXv2i7yBMDcQ7o5DSh/g21/1IQ7cJ5rJVnpCpw6pb5Td2ww9\\\\n\" +\n                \"6L90xrHSX13n90xIctglEKiMvAoB0UBQRlFG2qL1IgmhpVYBuiqLPIsaRbj2Bthd\\\\n\" +\n                \"nmsvDBflruBcjuimmRyozOVT1Cgw+xxw7nMMYDDs9iDqSgYnuLJZRYiHDVTna/Vx\\\\n\" +\n                \"UB6pS3TuoOKzJKuYL3lu2Yvjp0wTOXmaEg9wW9BqpIxu3U0Hd2ScEEOGQ+b3VEyp\\\\n\" +\n                \"IwwSk9KcPFs=\\\\n\" +\n                \"-----END CERTIFICATE-----\";\n        String issuingCa = \"-----BEGIN CERTIFICATE-----\\\\n\" +\n                \"MIIDNDCCAhygAwIBAgIUOM/FWyCOxZuYgAanfIk+11NQHLswDQYJKoZIhvcNAQEL\\\\n\" +\n                \"BQAwFjEUMBIGA1UEAxMLbXl2YXVsdC5jb20wHhcNMTgwNjIyMTU1OTI3WhcNMTgw\\\\n\" +\n                \"NzI0MTU1OTU3WjAtMSswKQYDVQQDEyJteXZhdWx0LmNvbSBJbnRlcm1lZGlhdGUg\\\\n\" +\n                \"QXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt6EKCNbb\\\\n\" +\n                \"+02nY/jM8EIwJ8moLUo6hCqOsEb4jFWYLjbInKICqdO36KsUEQL9W9Kq6LGqdZV4\\\\n\" +\n                \"cOYbpWYXr20Ni3p7hSgIttX+LJVPnm9g4Yc/71Wtzv9YsFXsudwQXE+iG+eBH2V2\\\\n\" +\n                \"kHbqANh/8ZXDzhZUlNecgR44YOOmS8z0nh3fOYwBu4eTazBvRk9PaUqS6VPtgqNF\\\\n\" +\n                \"sUAa7rszmOTRxVVsAN+O/HS08/+vwkIgvgTV849Pvb6diBlBWBc1LOOVuV+UWEsl\\\\n\" +\n                \"jfmhCM/nHqtGxg/cnPEV35WjAZH+ND+nkC2wRKaxfxf3B03MUm6WwIrRZrhMsBt2\\\\n\" +\n                \"OeEabv+NxKydDwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw\\\\n\" +\n                \"AwEB/zAdBgNVHQ4EFgQUTu0jLnLe15sBQhrYSEbXAILoLkMwHwYDVR0jBBgwFoAU\\\\n\" +\n                \"LzwRuDNnbxWfzW/iiM+Ek963I28wDQYJKoZIhvcNAQELBQADggEBADgwe8v8YPkJ\\\\n\" +\n                \"8Rsx9VkACu6IZ8hDkhDEe82wtU9BdzyIahgPgSwbjoLSIxX9nN3b8ifX1ZNgeio7\\\\n\" +\n                \"hkCQ8q0s3Eor479IqXv2i7yBMDcQ7o5DSh/g21/1IQ7cJ5rJVnpCpw6pb5Td2ww9\\\\n\" +\n                \"6L90xrHSX13n90xIctglEKiMvAoB0UBQRlFG2qL1IgmhpVYBuiqLPIsaRbj2Bthd\\\\n\" +\n                \"nmsvDBflruBcjuimmRyozOVT1Cgw+xxw7nMMYDDs9iDqSgYnuLJZRYiHDVTna/Vx\\\\n\" +\n                \"UB6pS3TuoOKzJKuYL3lu2Yvjp0wTOXmaEg9wW9BqpIxu3U0Hd2ScEEOGQ+b3VEyp\\\\n\" +\n                \"IwwSk9KcPFs=\\\\n\" +\n                \"-----END CERTIFICATE-----\";\n\n        String privateKey = \"-----BEGIN RSA PRIVATE KEY-----\\\\n\" +\n                \"MIIEowIBAAKCAQEArpT6tVs84CJmRUNzTZbvYu14uv1NWuNHLBc/Rr4aN5SDIGNi\\\\n\" +\n                \"ILccGmU0zub6ZSq/rFXzNn+hJ8b/PYuSH+idYBNUf1+9vF2KjXlwD/+Y4EcGqvUu\\\\n\" +\n                \"xINZXp+ToF3ZIzt0VVpGPMzJhvDentL4jYLPXGJvWyVfbvWyg3l2EWg3oklSIe+3\\\\n\" +\n                \"S+lWsLNOlYiXg80MaZA4mnkmPY8o4cAaK/p6sO1a76nPC42isuScw/a1D3L+ztIc\\\\n\" +\n                \"txxdPw97NmopTXS2q+FPahFYiP+Qc7MpHCZ72ixVt0amdQ5pVFKKJqTjFERTMG6x\\\\n\" +\n                \"CNxruKyl5pbuc3PXcVIg4507qe3tISuJjYamfwIDAQABAoIBABTuFXSCoLS6SwqI\\\\n\" +\n                \"wJ0PuFli4POCBLEdyF2X1+UyS1BYhLPwVkZXzY24jnEzrddNHbeaglMJUBfFurn1\\\\n\" +\n                \"LqqWp69qAdpXbxbTHBZD9dRlLz3MJhd+14GFwcQfW4KBXdPkf9jvvrXxU0PTQs1F\\\\n\" +\n                \"u7izcwq/XlxOCbfyytkKScZieTECaGmy7l6kJphaFP7m8eQ6vwI9LZXeFvA4URLJ\\\\n\" +\n                \"IzxM36Y/DkY+ME5AWxc9L+bYZjGj4QjRtfe27Dpy6FyrZg99pFfhoU/mWop3dh9z\\\\n\" +\n                \"rHBIvBppYUlp9BBBnOBxDTcmTh+dYGvApIud2gkpy3Om1BxCPXf7/TQgz3GQEnJW\\\\n\" +\n                \"JVaE94ECgYEAwSl2a7NPOqP4pYeKjRcdgwaI+lmIRjT6oNgHH4VM1aGolb8vYvdP\\\\n\" +\n                \"1VMwmMsDpxygX2p7gp9tbt2ZIcvwKBrIw6QTtS6kqxSqOSMlleeaHdd/WHYUmV9+\\\\n\" +\n                \"xiU5uHEWwjYqYivVXo1br06eXTE7zD2bDg9hZHDgCRRGXc6IvniQpr8CgYEA52As\\\\n\" +\n                \"I047YoJUX3OWE7Rxp6gIIO2St+HEKEZiV38m+7mWJOghUvGVssy/WYAPEvcGBAQp\\\\n\" +\n                \"zty2daaZBevFeW499N7haAFfoVbxpvtCR9fxheL1EkbsFMRPpCjgRGzhI7Rx2QAi\\\\n\" +\n                \"D5URL2WppkVDa+BW0wUIczL5jd5L57z3MT0XsEECgYEAviHO89JLEYCnVmAlbB2t\\\\n\" +\n                \"qfQ7zplkfx7U+I/L6yXt7Ha0l7nZrgObrHK3ah6jGNIftev9aST+tdsgSVkRqpg6\\\\n\" +\n                \"uACAeZ5Q7iloKNfEvlp7pBYjvnJ0ckfCZM3tk/SVH1Prwjg9TVW9QsETNs4oezDE\\\\n\" +\n                \"uEFBb3l/vNAdN2b9yOaqE8cCgYAMZ/G16unwPEC95Xq0j8ZQUQgui86EIYzdA/kd\\\\n\" +\n                \"6+lxMeBFFlVDF0UJk0TnTaCBSdF+waJkPx1hbY9i6+NowWp9CL5ZT0mLYxgN9gb1\\\\n\" +\n                \"xzRiE2tEkZzy+Bu1F6P+xz/DJFe+ZO1unHWRbwgLrEcTL7I4Glr7ok4TN0omoNE4\\\\n\" +\n                \"SKhOgQKBgDOZMbY2zzwozBQG5vxdxndIGmG874CRJ5CiS/hfTE0Gxdt54B9VadNU\\\\n\" +\n                \"ouZSwidB4YQH2aYHH1aUGhPemExztAcDJz2UholDUoj3v+ft6jCuMjb1loofqJeW\\\\n\" +\n                \"+hgtD7tH5sihc8tKYHXg6IfrZLdmUbWWj0qK6ow0hzSFNuJgZB+5\\\\n\" +\n                \"-----END RSA PRIVATE KEY-----\";\n\n        VaultResponseData vaultResponseData = new VaultResponseData();\n        vaultResponseData.setPrivate_key(privateKey);\n        vaultResponseData.setCertificate(certificate);\n        vaultResponseData.setCa_chain(Collections.singletonList(caChain));\n        vaultResponseData.setIssuing_ca(issuingCa);\n        return vaultResponseData;\n    }\n\n    private Vault generateVaultResource() {\n        Vault vault = new Vault();\n        vault.setMetadata(\n                new ObjectMetaBuilder().withName(\"pki\").withNamespace(\"default\").withUid(UUID.randomUUID().toString()).build()\n        );\n\n        VaultSpec spec = new VaultSpec();\n        spec.setType(VaultType.PKI);\n        spec.setPath(\"testpki/issue/testrole\");\n\n        VaultPkiConfiguration vaultPkiConfiguration = new VaultPkiConfiguration();\n        vaultPkiConfiguration.setCommonName(\"test.url.de\");\n        vaultPkiConfiguration.setTtl(\"10m\");\n        spec.setPkiConfiguration(vaultPkiConfiguration);\n\n        vault.setSpec(spec);\n\n        return vault;\n    }\n\n    private LocalDateTime convertDate(Date date) {\n        return date.toInstant()\n                .atZone(ZoneId.of(\"UTC\"))\n                .toLocalDateTime().truncatedTo(ChronoUnit.MINUTES);\n    }\n\n    private LocalDateTime parseDate(String date) throws ParseException {\n        SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT);\n        TimeZone tz = TimeZone.getTimeZone(\"UTC\");\n        format.setTimeZone(tz);\n        return convertDate(format.parse(date));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/de/koudingspawn/vault/PKITest.java",
    "content": "package de.koudingspawn.vault;\n\nimport com.github.tomakehurst.wiremock.client.WireMock;\nimport com.github.tomakehurst.wiremock.junit.WireMockClassRule;\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.crd.VaultPkiConfiguration;\nimport de.koudingspawn.vault.crd.VaultSpec;\nimport de.koudingspawn.vault.crd.VaultType;\nimport de.koudingspawn.vault.kubernetes.EventHandler;\nimport de.koudingspawn.vault.kubernetes.scheduler.impl.CertRefresh;\nimport de.koudingspawn.vault.vault.impl.pki.VaultResponseData;\nimport io.fabric8.kubernetes.api.model.DeletionPropagation;\nimport io.fabric8.kubernetes.api.model.ObjectMetaBuilder;\nimport io.fabric8.kubernetes.api.model.Secret;\nimport io.fabric8.kubernetes.client.DefaultKubernetesClient;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.KubernetesClientBuilder;\nimport org.bouncycastle.asn1.x500.X500Name;\nimport org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;\nimport org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;\nimport org.bouncycastle.jce.provider.BouncyCastleProvider;\nimport org.bouncycastle.operator.ContentSigner;\nimport org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;\nimport org.junit.*;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport java.math.BigInteger;\nimport java.security.KeyPair;\nimport java.security.KeyPairGenerator;\nimport java.security.Security;\nimport java.security.cert.Certificate;\nimport java.text.ParseException;\nimport java.text.SimpleDateFormat;\nimport java.time.Instant;\nimport java.time.LocalDateTime;\nimport java.time.ZoneId;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Base64;\nimport java.util.Date;\nimport java.util.TimeZone;\nimport java.util.UUID;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.*;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static de.koudingspawn.vault.Constants.*;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest(\n        properties = {\n                \"kubernetes.vault.url=http://localhost:8204/v1/\"\n        }\n)\npublic class PKITest {\n\n    static {\n        Security.addProvider(new BouncyCastleProvider());\n    }\n\n    @ClassRule\n    public static final WireMockClassRule wireMockClassRule =\n            new WireMockClassRule(wireMockConfig().port(8204));\n\n    @Rule\n    public WireMockClassRule instanceRule = wireMockClassRule;\n\n    @Autowired\n    public EventHandler handler;\n\n    @Autowired\n    public KubernetesClient client;\n\n    @org.springframework.boot.test.context.TestConfiguration\n    static class KindConfig {\n\n        @Bean\n        @Primary\n        public KubernetesClient client() {\n            return new KubernetesClientBuilder().build();\n        }\n\n    }\n\n    @Autowired\n    CertRefresh certRefresh;\n\n    @Before\n    public void before() {\n        WireMock.resetAllScenarios();\n        client.secrets().inAnyNamespace().delete();\n\n        TestHelper.generateLookupSelfStub();\n    }\n\n    @Test\n    public void shouldGeneratePkiFromVaultResource() throws Exception {\n        Date startDate = new Date();\n        VaultResponseData keyPair = generateKeyPair(startDate, 60L);\n        Vault vaultResource = generateVaultResource();\n\n\n        stubFor(post(urlEqualTo(\"/v1/testpki/issue/testrole\"))\n                .withRequestBody(matchingJsonPath(\"$.common_name\", containing(\"test.url.de\")))\n                .withRequestBody(matchingJsonPath(\"$.ttl\", containing(\"10m\")))\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\n                                String.format(\"\"\"\n                                        {\n                                          \"request_id\": \"6cc090a8-3821-8244-73e4-5ab62b605587\",\n                                          \"lease_id\": \"\",\n                                          \"renewable\": false,\n                                          \"lease_duration\": 2764800,\n                                          \"data\": {\n                                              \"certificate\": \"%s\",\n                                              \"private_key\": \"%s\"\n                                          },\n                                          \"wrap_info\": null,\n                                          \"warnings\": null,\n                                          \"auth\": null\n                                        }\"\"\", keyPair.getCertificate(), keyPair.getPrivate_key())\n                        )));\n\n        handler.addHandler(vaultResource);\n\n        Secret secret = client.secrets().inNamespace(\"default\").withName(\"pki\").get();\n\n        // metadata\n        assertEquals(\"pki\", secret.getMetadata().getName());\n        assertEquals(\"default\", secret.getMetadata().getNamespace());\n        assertEquals(\"Opaque\", secret.getType());\n        assertNotNull(secret.getMetadata().getAnnotations().get(\"vault.koudingspawn.de\" + LAST_UPDATE_ANNOTATION));\n\n        // compare date\n        String formatedExpirationDate = secret.getMetadata().getAnnotations().get(\"vault.koudingspawn.de\" + COMPARE_ANNOTATION);\n        LocalDateTime parsedCompareDate = parseDate(formatedExpirationDate);\n        LocalDateTime expirationDate = convertDate(startDate).plusMinutes(1);\n        assertEquals(expirationDate, parsedCompareDate);\n\n        // body\n        String crtB64 = secret.getData().get(\"tls.crt\");\n        String crt = new String(java.util.Base64.getDecoder().decode(crtB64));\n        String keyB64 = secret.getData().get(\"tls.key\");\n        String key = new String(java.util.Base64.getDecoder().decode(keyB64));\n\n        // not so nice, but wiremock expects double escaping\n        assertEquals(keyPair.getCertificate().replaceAll(\"\\\\\\\\n\", \"\"), crt.replaceAll(\"\\\\n\", \"\"));\n        assertEquals(keyPair.getPrivate_key().replaceAll(\"\\\\\\\\n\", \"\"), key.replaceAll(\"\\\\n\", \"\"));\n    }\n\n    @After\n    @Before\n    public void cleanup() {\n        Secret secret = client.secrets().inNamespace(\"default\").withName(\"pki\").get();\n        if (secret != null) {\n            client.secrets().inNamespace(\"default\").withName(\"pki\").withPropagationPolicy(DeletionPropagation.BACKGROUND).delete();\n        }\n    }\n\n    private VaultResponseData generateKeyPair(Date startDate, long valid) throws Exception {\n        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(\"RSA\", BouncyCastleProvider.PROVIDER_NAME);\n        keyPairGenerator.initialize(2048);\n        KeyPair keyPair = keyPairGenerator.generateKeyPair();\n\n        X500Name x500Name = new X500Name(\"CN=Test\");\n        BigInteger certSerialNumber = BigInteger.valueOf(System.currentTimeMillis());\n        String signatureAlgorithm = \"SHA256WithRSA\";\n        ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm)\n                .build(keyPair.getPrivate());\n        Instant endDate = startDate.toInstant().plus(valid, ChronoUnit.SECONDS);\n\n        JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(\n                x500Name, certSerialNumber, startDate, Date.from(endDate), x500Name,\n                keyPair.getPublic());\n\n        Certificate certificate = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME)\n                .getCertificate(certBuilder.build(contentSigner));\n\n        byte[] encodedPrivateKey = keyPair.getPrivate().getEncoded();\n        byte[] encodedPublicKey = certificate.getEncoded();\n\n        String privateKeySb = \"-----BEGIN PRIVATE KEY-----\\n\" +\n                Base64.getMimeEncoder().encodeToString(encodedPrivateKey) +\n                \"\\n-----END PRIVATE KEY-----\";\n        String publicKey = \"-----BEGIN PUBLIC KEY-----\\n\" +\n                Base64.getMimeEncoder().encodeToString(encodedPublicKey) +\n                \"\\n-----END PUBLIC KEY-----\";\n\n        privateKeySb = privateKeySb.replaceAll(\"\\\\n\", \"\\\\\\\\n\");\n        privateKeySb = privateKeySb.replaceAll(\"\\\\r\", \"\");\n\n        publicKey = publicKey.replaceAll(\"\\\\n\", \"\\\\\\\\n\");\n        publicKey = publicKey.replaceAll(\"\\\\r\", \"\");\n\n        VaultResponseData vaultResponseData = new VaultResponseData();\n        vaultResponseData.setPrivate_key(privateKeySb);\n        vaultResponseData.setCertificate(publicKey);\n        return vaultResponseData;\n    }\n\n    private Vault generateVaultResource() {\n        Vault vault = new Vault();\n        vault.setMetadata(\n                new ObjectMetaBuilder().withName(\"pki\").withNamespace(\"default\").withUid(UUID.randomUUID().toString()).build()\n        );\n\n        VaultSpec spec = new VaultSpec();\n        spec.setType(VaultType.PKI);\n        spec.setPath(\"testpki/issue/testrole\");\n\n        VaultPkiConfiguration vaultPkiConfiguration = new VaultPkiConfiguration();\n        vaultPkiConfiguration.setCommonName(\"test.url.de\");\n        vaultPkiConfiguration.setTtl(\"10m\");\n        spec.setPkiConfiguration(vaultPkiConfiguration);\n\n        vault.setSpec(spec);\n\n        return vault;\n    }\n\n    private LocalDateTime convertDate(Date date) {\n        return date.toInstant()\n                .atZone(ZoneId.of(\"UTC\"))\n                .toLocalDateTime().truncatedTo(ChronoUnit.MINUTES);\n    }\n\n    private LocalDateTime parseDate(String date) throws ParseException {\n        SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT);\n        TimeZone tz = TimeZone.getTimeZone(\"UTC\");\n        format.setTimeZone(tz);\n        return convertDate(format.parse(date));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/de/koudingspawn/vault/PropertiesTest.java",
    "content": "package de.koudingspawn.vault;\n\nimport com.github.tomakehurst.wiremock.client.WireMock;\nimport com.github.tomakehurst.wiremock.junit.WireMockClassRule;\nimport com.google.common.collect.ImmutableMap;\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.crd.VaultPropertiesConfiguration;\nimport de.koudingspawn.vault.crd.VaultSpec;\nimport de.koudingspawn.vault.crd.VaultType;\nimport de.koudingspawn.vault.kubernetes.EventHandler;\nimport de.koudingspawn.vault.kubernetes.scheduler.impl.CertRefresh;\nimport de.koudingspawn.vault.vault.VaultService;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport io.fabric8.kubernetes.api.model.DeletionPropagation;\nimport io.fabric8.kubernetes.api.model.ObjectMetaBuilder;\nimport io.fabric8.kubernetes.api.model.Secret;\nimport io.fabric8.kubernetes.client.DefaultKubernetesClient;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.KubernetesClientBuilder;\nimport org.junit.*;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.core.io.ClassPathResource;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.util.Base64;\nimport java.util.HashMap;\nimport java.util.UUID;\n\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static de.koudingspawn.vault.Constants.LAST_UPDATE_ANNOTATION;\nimport static org.junit.Assert.*;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest(\n        properties = {\n                \"kubernetes.vault.url=http://localhost:8208/v1/\",\n                \"kubernetes.vault.token=c73ab0cb-41e6-b89c-7af6-96b36f1ac87b\"\n        }\n\n)\npublic class PropertiesTest {\n\n    @ClassRule\n    public static final WireMockClassRule wireMockClassRule =\n            new WireMockClassRule(wireMockConfig().port(8208));\n\n    @Rule\n    public WireMockClassRule instanceRule = wireMockClassRule;\n\n    @Autowired\n    public EventHandler handler;\n\n    @Autowired\n    public VaultService vaultService;\n\n    @Autowired\n    public KubernetesClient client;\n\n    @org.springframework.boot.test.context.TestConfiguration\n    static class KindConfig {\n\n        @Bean\n        @Primary\n        public KubernetesClient client() {\n            return new KubernetesClientBuilder().build();\n        }\n\n    }\n\n    @Autowired\n    CertRefresh certRefresh;\n\n    @Before\n    public void before() {\n        WireMock.resetAllScenarios();\n        client.secrets().inAnyNamespace().delete();\n\n        TestHelper.generateLookupSelfStub();\n    }\n\n    @Test\n    public void shouldRenderPropertiesFile() throws IOException {\n        TestHelper.generateKVStup(\"kv/key\", ImmutableMap.of(\"value\", \"kv1content\"));\n        TestHelper.generateKV2Stup(\"kv2/key\", ImmutableMap.of(\"value\", \"kv2content\", \"value2\", \"kv3content\"));\n\n        Vault vault = generatePropertiesManifest(\"properties\");\n        handler.addHandler(vault);\n\n        Secret secret = client.secrets().inNamespace(\"default\").withName(\"properties\").get();\n        assertEquals(\"properties\", secret.getMetadata().getName());\n        assertEquals(\"default\", secret.getMetadata().getNamespace());\n        assertEquals(\"Opaque\", secret.getType());\n        assertNotNull(secret.getMetadata().getAnnotations().get(\"vault.koudingspawn.de\" + LAST_UPDATE_ANNOTATION));\n        String renderedB64Properties = secret.getData().get(\"test.properties\");\n        String renderedProperties = new String(Base64.getDecoder().decode(renderedB64Properties));\n\n        assertTrue(renderedProperties.contains(\"test=kv1content\"));\n        assertTrue(renderedProperties.contains(\"test2=kv2content\"));\n        assertTrue(renderedProperties.contains(\"test3=contextvalue\"));\n        assertTrue(renderedProperties.contains(\"spring.jpa.properties.hibernate.dialect=class.module.classLoader.resources.context.parent.pipeline.first\"));\n    }\n\n    @Test(expected = SecretNotAccessibleException.class)\n    public void shouldFailRenderSecret() throws SecretNotAccessibleException, IOException {\n        TestHelper.generateKVStup(\"kv/key\", ImmutableMap.of(\"value\", \"kv1content\"));\n        TestHelper.generateKV2Stup(\"kv2/key\", ImmutableMap.of(\"value\", \"kv2content\"));\n\n        Vault vault = generatePropertiesManifest(\"properties-1\");\n        vaultService.generateSecret(vault);\n    }\n\n    @After\n    @Before\n    public void cleanup() {\n        Secret secret = client.secrets().inNamespace(\"default\").withName(\"properties\").get();\n        if (secret != null) {\n            client.secrets().inNamespace(\"default\").withName(\"properties\").withPropagationPolicy(DeletionPropagation.BACKGROUND).delete();\n        }\n    }\n\n    static Vault generatePropertiesManifest(String name) throws IOException {\n        HashMap<String, String> properties = new HashMap<>();\n        File file = new ClassPathResource(\"test.properties\").getFile();\n        String content = new String(Files.readAllBytes(file.toPath()));\n        properties.put(\"test.properties\", content);\n\n        HashMap<String, String> context = new HashMap<>();\n        context.put(\"contextkey\", \"contextvalue\");\n\n        VaultSpec vaultSpec = new VaultSpec();\n        vaultSpec.setType(VaultType.PROPERTIES);\n\n        VaultPropertiesConfiguration vaultPropertiesConfiguration = new VaultPropertiesConfiguration();\n        vaultPropertiesConfiguration.setFiles(properties);\n        vaultPropertiesConfiguration.setContext(context);\n        vaultSpec.setPropertiesConfiguration(vaultPropertiesConfiguration);\n\n        Vault vault = new Vault();\n        vault.setMetadata(\n                new ObjectMetaBuilder().withName(name).withNamespace(\"default\").withUid(UUID.randomUUID().toString()).build()\n        );\n        vault.setSpec(vaultSpec);\n\n        return vault;\n    }\n\n}\n\n"
  },
  {
    "path": "src/test/java/de/koudingspawn/vault/TestHelper.java",
    "content": "package de.koudingspawn.vault;\n\nimport org.json.JSONObject;\n\nimport java.util.Map;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.*;\n\npublic class TestHelper {\n\n    public static void generateLookupSelfStub() {\n        stubFor(get(urlEqualTo(\"/v1/auth/token/lookup-self\"))\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"\"\"\n                                {\n                                    \"request_id\": \"200ef4ee-7ca7-9d38-2e63-6002454e00d7\",\n                                    \"lease_id\": \"\",\n                                    \"renewable\": false,\n                                    \"lease_duration\": 0,\n                                    \"data\": {\n                                        \"accessor\": \"c69c3bd7-c142-c655-2757-77bfdc86b04a\",\n                                        \"creation_time\": 1536033750,\n                                        \"creation_ttl\": 0,\n                                        \"display_name\": \"root\",\n                                        \"entity_id\": \"\",\n                                        \"expire_time\": null,\n                                        \"explicit_max_ttl\": 0,\n                                        \"id\": \"c73ab0cb-41e6-b89c-7af6-96b36f1ac87b\",\n                                        \"meta\": null,\n                                        \"num_uses\": 0,\n                                        \"orphan\": true,\n                                        \"path\": \"auth/token/root\",\n                                        \"policies\": [\n                                            \"root\"\n                                        ],\n                                        \"ttl\": 0\n                                    },\n                                    \"wrap_info\": null,\n                                    \"warnings\": null,\n                                    \"auth\": null\n                                }\"\"\")));\n    }\n\n    public static void generateKVStup(String path, Map<String, String> value) {\n        JSONObject jsonObject = new JSONObject(value);\n\n\n        stubFor(get(urlPathMatching(\"/v1/\" + path))\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"{\\\"request_id\\\":\\\"6cc090a8-3821-8244-73e4-5ab62b605587\\\",\\\"lease_id\\\":\\\"\\\",\\\"renewable\\\":false,\\\"lease_duration\\\":2764800,\\\"data\\\":\" + jsonObject + \",\\\"wrap_info\\\":null,\\\"warnings\\\":null,\\\"auth\\\":null}\")));\n\n    }\n\n    public static void generateKV2Stup(String path, Map<String, String> value) {\n        JSONObject jsonObject = new JSONObject(value);\n        String[] splittedPath = path.split(\"/\");\n\n        stubFor(get(urlPathMatching(\"/v1/\" + splittedPath[0] + \"/data/\" + splittedPath[1]))\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withHeader(\"Content-Type\", \"application/json\")\n                        .withBody(\"{\\n\" +\n                                \"  \\\"request_id\\\": \\\"1cfee2a6-318a-ea12-f5b5-6fd52d74d2c6\\\",\\n\" +\n                                \"  \\\"lease_id\\\": \\\"\\\",\\n\" +\n                                \"  \\\"renewable\\\": false,\\n\" +\n                                \"  \\\"lease_duration\\\": 0,\\n\" +\n                                \"  \\\"data\\\": {\\n\" +\n                                \"    \\\"data\\\": \" + jsonObject + \",\\n\" +\n                                \"    \\\"metadata\\\": {\\n\" +\n                                \"      \\\"created_time\\\": \\\"2018-12-10T18:59:53.337997525Z\\\",\\n\" +\n                                \"      \\\"deletion_time\\\": \\\"\\\",\\n\" +\n                                \"      \\\"destroyed\\\": false,\\n\" +\n                                \"      \\\"version\\\": 1\\n\" +\n                                \"    }\\n\" +\n                                \"  },\\n\" +\n                                \"  \\\"wrap_info\\\": null,\\n\" +\n                                \"  \\\"warnings\\\": null,\\n\" +\n                                \"  \\\"auth\\\": null\\n\" +\n                                \"}\")));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/de/koudingspawn/vault/admissionreview/AdmissionReviewTest.java",
    "content": "package de.koudingspawn.vault.admissionreview;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.crd.VaultList;\nimport de.koudingspawn.vault.vault.VaultSecret;\nimport de.koudingspawn.vault.vault.VaultService;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport io.fabric8.kubernetes.client.dsl.MixedOperation;\nimport io.fabric8.kubernetes.client.dsl.Resource;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mockito;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.test.context.ActiveProfiles;\nimport org.springframework.test.context.junit4.SpringRunner;\nimport org.springframework.test.web.servlet.MockMvc;\n\nimport java.util.HashMap;\n\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;\nimport static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;\n\n@RunWith(SpringRunner.class)\n@SpringBootTest(\n        properties = {\n                \"kubernetes.vault.url=http://localhost:8206/v1/\",\n                \"kubernetes.vault.token=c73ab0cb-41e6-b89c-7af6-96b36f1ac87b\"\n        }\n)\n@AutoConfigureMockMvc\n@ActiveProfiles(\"test\")\npublic class AdmissionReviewTest {\n\n    @MockBean\n    VaultService vaultService;\n\n    @MockBean\n    public MixedOperation<Vault, VaultList, Resource<Vault>> customResource;\n\n    @Autowired\n    private MockMvc mvc;\n\n    @Test\n    public void shouldFailWithInvalidRequest() throws Exception {\n        SecretNotAccessibleException secretException = new SecretNotAccessibleException(\"Secret is not accessible\");\n        Mockito.when(vaultService.generateSecret(any())).thenThrow(secretException);\n\n        mvc.perform(post(\"/validation/vault-crd\").content(\"\"\"\n                        {\n                          \"apiVersion\": \"admission.k8s.io/v1\",\n                          \"kind\": \"AdmissionReview\",\n                          \"request\": {\n                            \"uid\": \"705ab4f5-6393-11e8-b7cc-42010a800002\",\n                            \"object\": {\n                              \"apiVersion\": \"koudingspawn.de/v1\",\n                              \"kind\": \"Vault\",\n                              \"metadata\": {\n                                \"name\": \"test-vault\",\n                                \"namespace\": \"default\"\n                              },\n                              \"spec\": {\n                                \"type\": \"KEYVALUE\",\n                                \"path\": \"secret/qweasd\"\n                              }\n                            }\n                          }\n                        }\n                        \"\"\").contentType(\"application/json\"))\n                .andDo(print())\n                .andExpect(status().isOk())\n                .andExpect(jsonPath(\"$.response.uid\").value(\"705ab4f5-6393-11e8-b7cc-42010a800002\"))\n                .andExpect(jsonPath(\"$.response.allowed\").value(\"false\"))\n                .andExpect(jsonPath(\"$.response.status.code\").value(\"400\"))\n                .andExpect(jsonPath(\"$.response.status.message\").value(\"Secret is not accessible\"));\n    }\n\n    @Test\n    public void shouldReturnValidValue() throws Exception {\n        VaultSecret vaultSecret = new VaultSecret(new HashMap<>(), \"qweasd\");\n        Mockito.when(vaultService.generateSecret(any())).thenReturn(vaultSecret);\n\n        mvc.perform(post(\"/validation/vault-crd\").content(\"\"\"\n                        {\n                          \"apiVersion\": \"admission.k8s.io/v1\",\n                          \"kind\": \"AdmissionReview\",\n                          \"request\": {\n                            \"uid\": \"705ab4f5-6393-11e8-b7cc-42010a800002\",\n                            \"object\": {\n                              \"apiVersion\": \"koudingspawn.de/v1\",\n                              \"kind\": \"Vault\",\n                              \"metadata\": {\n                                \"name\": \"test-vault\",\n                                \"namespace\": \"default\"\n                              },\n                              \"spec\": {\n                                \"type\": \"KEYVALUE\",\n                                \"path\": \"secret/qweasd\"\n                              }\n                            }\n                          }\n                        }\"\"\").contentType(\"application/json\"))\n                .andDo(print())\n                .andExpect(status().isOk())\n                .andExpect(jsonPath(\"$.response.uid\").value(\"705ab4f5-6393-11e8-b7cc-42010a800002\"))\n                .andExpect(jsonPath(\"$.response.allowed\").value(\"true\"));\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/de/koudingspawn/vault/kubernetes/EventHandlerTest.java",
    "content": "package de.koudingspawn.vault.kubernetes;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.crd.VaultSpec;\nimport de.koudingspawn.vault.kubernetes.event.EventNotification;\nimport de.koudingspawn.vault.vault.VaultSecret;\nimport de.koudingspawn.vault.vault.VaultService;\nimport de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mock;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport java.util.HashMap;\n\nimport static org.mockito.Mockito.*;\n\n@RunWith(SpringRunner.class)\npublic class EventHandlerTest {\n\n    @Mock\n    private VaultService vaultService;\n\n    @Mock\n    private KubernetesService kubernetesService;\n\n    @Mock\n    private ChangeAdjustmentService changeAdjustmentService;\n\n    @Mock\n    private EventNotification eventNotification;\n\n    private EventHandler eventHandler;\n\n    @Before\n    public void setup() {\n        eventHandler = new EventHandler(vaultService, kubernetesService, changeAdjustmentService, eventNotification, true);\n    }\n\n\n    @Test\n    public void shouldGenerateKubernetesSecret() throws SecretNotAccessibleException {\n        Vault vault = new Vault();\n        VaultSecret vaultSecret = new VaultSecret(new HashMap<>(), \"COMPARE\");\n\n        when(vaultService.generateSecret(vault)).thenReturn(vaultSecret);\n        eventHandler.addHandler(vault);\n\n        verify(kubernetesService, times(1)).createSecret(vault, vaultSecret);\n    }\n\n    @Test\n    public void shouldDoNotingIfSecretForVaultAlreadyExists() {\n        Vault vault = new Vault();\n\n        when(kubernetesService.exists(vault)).thenReturn(true);\n        eventHandler.addHandler(vault);\n\n        verify(kubernetesService, never()).createSecret(any(), any());\n    }\n\n    @Test\n    public void shouldDoNothingIfGenerateSecretFails() throws SecretNotAccessibleException {\n        Vault vault = new Vault();\n\n        when(vaultService.generateSecret(vault)).thenThrow(SecretNotAccessibleException.class);\n        eventHandler.addHandler(vault);\n\n        verify(kubernetesService, never()).createSecret(any(), any());\n    }\n\n    @Test\n    public void shouldModifySecret() throws SecretNotAccessibleException {\n        Vault vault = new Vault();\n        vault.setSpec(new VaultSpec());\n        VaultSecret vaultSecret = new VaultSecret(new HashMap<>(), \"COMPARE\");\n\n        when(vaultService.generateSecret(vault)).thenReturn(vaultSecret);\n        eventHandler.modifyHandler(vault);\n\n        verify(kubernetesService, times(1)).modifySecret(vault, vaultSecret);\n    }\n\n    @Test\n    public void shouldDoNothingIfCreateSecretForModificationFails() throws SecretNotAccessibleException {\n        Vault vault = new Vault();\n        vault.setSpec(new VaultSpec());\n\n        when(vaultService.generateSecret(vault)).thenThrow(SecretNotAccessibleException.class);\n        eventHandler.modifyHandler(vault);\n\n        verify(kubernetesService, never()).modifySecret(any(), any());\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/de/koudingspawn/vault/kubernetes/KubernetesServiceTest.java",
    "content": "package de.koudingspawn.vault.kubernetes;\n\nimport de.koudingspawn.vault.crd.Vault;\nimport de.koudingspawn.vault.kubernetes.cache.SecretCache;\nimport de.koudingspawn.vault.vault.VaultSecret;\nimport io.fabric8.kubernetes.api.model.*;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.KubernetesClientBuilder;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport java.util.HashMap;\nimport java.util.UUID;\n\nimport static de.koudingspawn.vault.Constants.COMPARE_ANNOTATION;\nimport static org.junit.Assert.*;\n\n\n@RunWith(SpringRunner.class)\npublic class KubernetesServiceTest {\n\n    private static final String COMPARE = \"COMPARE\";\n    private static final String CRDNAME = \"CRDNAME\";\n    private static final String CRDGROUP = \"CRDGROUP\";\n\n    private static final String NAMESPACE = \"test\";\n    private static final String SECRETNAME = \"testsecret\";\n\n    @Autowired\n    public KubernetesClient client;\n\n    private KubernetesService kubernetesService;\n\n    @org.springframework.boot.test.context.TestConfiguration\n    static class KindConfig {\n\n        @Bean\n        @Primary\n        public KubernetesClient client() {\n            return new KubernetesClientBuilder().build();\n        }\n    }\n\n    @Before\n    public void setUp() {\n        SecretCache secretCache = new SecretCache(client, false);\n        kubernetesService = new KubernetesService(client, secretCache, CRDNAME, CRDGROUP);\n\n        Namespace ns = new NamespaceBuilder().withMetadata(new ObjectMetaBuilder().withName(NAMESPACE).build()).build();\n        client.namespaces().resource(ns).createOrReplace();\n    }\n\n    @Test\n    public void shouldCheckIfResourceExists() {\n        Vault vault = generateVault();\n\n        Secret testsecret = generateSecret();\n        client.secrets().inNamespace(NAMESPACE).resource(testsecret).create();\n\n        boolean exists = kubernetesService.exists(vault);\n\n        assertTrue(exists);\n    }\n\n    @Test\n    public void shouldFindNoResource() {\n        Vault vault = generateVault();\n\n        boolean exists = kubernetesService.exists(vault);\n\n        assertFalse(exists);\n    }\n\n    @Test\n    public void shouldCreateSecret() {\n        Vault vault = generateVault();\n        VaultSecret vaultSecret = generateVaultSecret();\n\n        kubernetesService.createSecret(vault, vaultSecret);\n\n        Secret secret = client.secrets().inNamespace(NAMESPACE).withName(SECRETNAME).get();\n        assertEquals(\"dmFsdWU=\", secret.getData().get(\"key\")); // value\n        assertEquals(\"Opaque\", secret.getType());\n        assertEquals(COMPARE, secret.getMetadata().getAnnotations().get(CRDNAME + COMPARE_ANNOTATION));\n        assertNotNull(secret.getMetadata().getAnnotations().get(CRDNAME + COMPARE_ANNOTATION));\n    }\n\n    @Test\n    public void shouldDeleteSecret() {\n        Secret secret = generateSecret();\n\n        client.secrets().inNamespace(NAMESPACE).resource(secret).create();\n\n        assertNotNull(client.secrets().inNamespace(NAMESPACE).withName(SECRETNAME).get());\n\n        kubernetesService.deleteSecret(generateVault().getMetadata());\n\n        assertNull(client.secrets().inNamespace(NAMESPACE).withName(SECRETNAME).get());\n    }\n\n    @Test\n    public void shouldModifySecret() {\n        Secret secret = generateSecret();\n        client.secrets().inNamespace(NAMESPACE).resource(secret).create();\n\n        Vault vault = generateVault();\n        HashMap<String, String> data = new HashMap<>();\n        data.put(\"key1\", \"dmFsdWUx\"); // value1\n        VaultSecret modifiedVaultSecret = new VaultSecret(data, COMPARE + \"NEW\");\n\n        kubernetesService.modifySecret(vault, modifiedVaultSecret);\n\n        Secret foundSecret = client.secrets().inNamespace(NAMESPACE).withName(SECRETNAME).get();\n\n        assertEquals(COMPARE + \"NEW\", foundSecret.getMetadata().getAnnotations().get(CRDNAME + COMPARE_ANNOTATION));\n        assertEquals(\"Opaque\", foundSecret.getType());\n        assertEquals(\"dmFsdWUx\", foundSecret.getData().get(\"key1\"));\n        assertNull(foundSecret.getData().get(\"key\"));\n    }\n\n    @After\n    @Before\n    public void cleanup() {\n        Secret secret = client.secrets().inNamespace(NAMESPACE).withName(SECRETNAME).get();\n        if (secret != null) {\n            client.secrets().inNamespace(NAMESPACE).withName(SECRETNAME).withPropagationPolicy(DeletionPropagation.BACKGROUND).delete();\n        }\n    }\n\n    private Secret generateSecret() {\n        HashMap<String, String> data = new HashMap<>();\n        data.put(\"key\", \"dmFsdWU=\"); // value\n\n        return new SecretBuilder()\n                .withNewMetadata()\n                .withName(SECRETNAME)\n                .endMetadata()\n                .addToData(data)\n                .build();\n    }\n\n    private VaultSecret generateVaultSecret() {\n        HashMap<String, String> data = new HashMap<>();\n        data.put(\"key\", \"dmFsdWU=\");\n        return new VaultSecret(data, COMPARE);\n    }\n\n    private Vault generateVault() {\n        Vault vault = new Vault();\n        ObjectMeta meta = new ObjectMeta();\n        meta.setNamespace(NAMESPACE);\n        meta.setName(SECRETNAME);\n        meta.setUid(UUID.randomUUID().toString());\n        vault.setMetadata(meta);\n\n        return vault;\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/de/koudingspawn/vault/vault/VaultHealthCheckTest.java",
    "content": "package de.koudingspawn.vault.vault;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.springframework.boot.actuate.health.Status;\nimport org.springframework.test.context.junit4.SpringRunner;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.Mockito.when;\n\n@RunWith(SpringRunner.class)\npublic class VaultHealthCheckTest {\n\n    @Mock\n    VaultCommunication vaultCommunication;\n\n    @InjectMocks\n    VaultHealthCheck vaultHealthCheck;\n\n    @Test\n    public void shouldReturnUnhealthyIfVaultCommunicationFails() {\n        when(vaultCommunication.isHealthy()).thenReturn(false);\n\n        assertEquals(Status.DOWN, vaultHealthCheck.health().getStatus());\n    }\n\n    @Test\n    public void shouldReturnHealthyResultIfVaultCommunicationWorks() {\n        when(vaultCommunication.isHealthy()).thenReturn(true);\n\n        assertEquals(Status.UP, vaultHealthCheck.health().getStatus());\n    }\n\n\n\n}"
  },
  {
    "path": "src/test/resources/application.properties",
    "content": "kubernetes.crd.group=something-not-garbage-collected.de\nkubernetes.crd.name=vault.koudingspawn.de\nkubernetes.vault.auth=token\nkubernetes.vault.token=50745c04-e6ef-dc59-0bae-7b4af8470fa6\nkubernetes.vault.role=admin\nkubernetes.interval=60\nkubernetes.jks.default-alias=main\nkubernetes.jks.default-password=changeit\nkubernetes.jks.default-secret-key-name=key.jks\nkubernetes.ownerreference-fix.enabled=true\n\nmanagement.endpoints.enabled-by-default=false\nmanagement.endpoint.health.enabled=true\n\nspring.main.allow-bean-definition-overriding=true\n"
  },
  {
    "path": "src/test/resources/test.properties",
    "content": "test={{ vault.lookup('kv/key', 'value') }}\n\ntest2={{ vault.lookupV2('kv2/key').get('value') }}\n\ntest3={{ contextkey }}\n\ntest4={{ vault.lookupV2('kv2/key', 'value2') }}\n\n# remidiation test spring4shell\nspring.jpa.properties.hibernate.dialect=class.module.classLoader.resources.context.parent.pipeline.first"
  },
  {
    "path": "src/test/resources/vault-crd.yaml",
    "content": "apiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n  name: vault.koudingspawn.de\nspec:\n  group: koudingspawn.de\n  scope: Namespaced\n  names:\n    plural: vault\n    singular: vault\n    kind: Vault\n    shortNames:\n      - vt\n  versions:\n    - name: v1\n      served: true\n      storage: true\n  validation:\n    openAPIV3Schema:\n      properties:\n        spec:\n          properties:\n            path:\n              type: string\n              pattern: '^.*?\\/.*?(\\/.*?)?$'\n            type:\n              type: string\n              enum:\n                - PKI\n                - PKIJKS\n                - CERT\n                - CERTJKS\n                - DOCKERCFG\n                - KEYVALUE\n                - KEYVALUEV2\n                - PROPERTIES\n            pkiConfiguration:\n              type: object\n              properties:\n                commonName:\n                  type: string\n                altNames:\n                  type: string\n                ipSans:\n                  type: string\n                ttl:\n                  type: string\n                  pattern: '^[0-9]{1,}[hm]$'\n            jksConfiguration:\n              type: object\n              properties:\n                password:\n                  type: string\n                alias:\n                  type: string\n                keyName:\n                  type: string\n                caAlias:\n                  type: string\n            versionConfiguration:\n              type: object\n              properties:\n                version:\n                  type: integer\n            propertiesConfiguration:\n              type: object\n              properties:\n                context:\n                  type: object\n                files:\n                  type: object\n            dockerCfgConfiguration:\n              type: object\n              properties:\n                type:\n                  type: string\n                  enum:\n                    - KEYVALUE\n                    - KEYVALUEV2\n                version:\n                  type: integer\n          required:\n            - type\n"
  }
]