├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── config.yml │ └── story.md ├── PULL_REQUEST_TEMPLATE.md ├── ct.yaml ├── renovate.json5 └── workflows │ ├── changelog.yml │ ├── push_pr.yml │ ├── release-chart.yml │ ├── release-integration.yml │ ├── repolinter.yml │ ├── security.yaml │ └── trigger-release.yml ├── .gitignore ├── .golangci.yml ├── .trivyignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── THIRD_PARTY_NOTICES.md ├── charts └── nri-metadata-injection │ ├── .helmignore │ ├── Chart.lock │ ├── Chart.yaml │ ├── README.md │ ├── README.md.gotmpl │ ├── ci │ └── test-values.yaml │ ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── admission-webhooks │ │ ├── job-patch │ │ │ ├── README.md │ │ │ ├── clusterrole.yaml │ │ │ ├── clusterrolebinding.yaml │ │ │ ├── job-createSecret.yaml │ │ │ ├── job-patchWebhook.yaml │ │ │ ├── psp.yaml │ │ │ ├── role.yaml │ │ │ ├── rolebinding.yaml │ │ │ └── serviceaccount.yaml │ │ └── mutatingWebhookConfiguration.yaml │ ├── cert-manager.yaml │ ├── deployment.yaml │ └── service.yaml │ ├── tests │ ├── cluster_test.yaml │ ├── job_serviceaccount_test.yaml │ ├── rbac_test.yaml │ └── volume_mounts_test.yaml │ └── values.yaml ├── cmd └── server │ └── main.go ├── docs ├── k8s-api-lifecycle.svg ├── lifecycle.md └── performance.md ├── e2e-tests ├── k8s-e2e-bootstraping.sh └── tests.sh ├── entrypoint.sh ├── go.mod ├── go.sum ├── openapi.yaml └── src └── server ├── readiness_probe.go ├── readiness_probe_test.go ├── testdata └── expectedAdmissionReviewPatch.json ├── webhook.go └── webhook_test.go /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This is a comment. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # These owners will be the default owners for everything in 5 | # the repo. Unless a later match takes precedence. 6 | 7 | * @newrelic/k8s-agents 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ^^ Provide a general summary of the issue in the title above. ^^ 11 | 12 | ## Description 13 | Describe the problem you're encountering. 14 | TIP: Do NOT share sensitive information, whether personal, proprietary, or otherwise! 15 | 16 | ## Expected Behavior 17 | Tell us what you expected to happen. 18 | 19 | ## [Troubleshooting](https://discuss.newrelic.com/t/troubleshooting-frameworks/108787) or [NR Diag](https://docs.newrelic.com/docs/using-new-relic/cross-product-functions/troubleshooting/new-relic-diagnostics) results 20 | Provide any other relevant log data. 21 | TIP: Scrub logs and diagnostic information for sensitive information 22 | 23 | ## Steps to Reproduce 24 | Please be as specific as possible. 25 | TIP: Link a sample application that demonstrates the issue. 26 | 27 | ## Your Environment 28 | Include as many relevant details about your environment as possible including the running version of New Relic software and any relevant configurations. 29 | 30 | ## Additional context 31 | Add any other context about the problem here. For example, relevant community posts or support tickets. 32 | 33 | ## For Maintainers Only or Hero Triaging this bug 34 | *Suggested Priority (P1,P2,P3,P4,P5):* 35 | *Suggested T-Shirt size (S, M, L, XL, Unknown):* 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Feature Request 4 | url: https://issues.newrelic.com 5 | about: We welcome feature requests from our open-source community through the New Relic Issue Tracker. 6 | - name: Troubleshooting 7 | url: https://github.com/newrelic/k8s-metadata-injection/blob/main/README.md#support 8 | about: Check out the README for troubleshooting directions 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/story.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Story 3 | about: Issue describing development work to fulfill a feature request 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | priority: '' 8 | --- 9 | ### Description 10 | _What's the goal of this unit of work? What is included? What isn't included?_ 11 | 12 | ### Acceptance Criteria 13 | _What tasks need to be accomplished to achieve the goal?_ 14 | 15 | ### Design Consideration/Limitations 16 | _Why is this the route we should take to achieve our goal?_ 17 | _What can't be achieved within this story?_ 18 | 19 | ### Dependencies 20 | _Do any other teams or parts of the New Relic product need to be considered?_ 21 | _Some common areas: UI, collector, documentation_ 22 | 23 | ### Additional context 24 | _What else should we know about this story that might not fit into the other categories?_ 25 | 26 | ### Estimates 27 | _Please provide initial t-shirt size. S = 1-3 days, M = 3-5 days (1 week), L = 1-2 weeks (1 sprint)_ 28 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 4 | ## Type of change 5 | 6 | 7 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 8 | - [ ] New feature / enhancement (non-breaking change which adds functionality) 9 | - [ ] Security fix 10 | - [ ] Bug fix (non-breaking change which fixes an issue) 11 | 12 | ## Checklist: 13 | 14 | 15 | - [ ] Add changelog entry following the [contributing guide](../CONTRIBUTING.md#pull-requests) 16 | - [ ] Documentation has been updated 17 | - [ ] This change requires changes in testing: 18 | - [ ] unit tests 19 | - [ ] E2E tests 20 | -------------------------------------------------------------------------------- /.github/ct.yaml: -------------------------------------------------------------------------------- 1 | # Chart linter defaults to `master` branch so we need to specify this as the default branch 2 | # or `cl` will fail with a not-so-helpful error that says: 3 | # "Error linting charts: Error identifying charts to process: Error running process: exit status 128" 4 | target-branch: main 5 | 6 | chart-repos: 7 | - newrelic=https://helm-charts.newrelic.com 8 | 9 | # Charts will be released manually. 10 | check-version-increment: false 11 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "github>newrelic/k8s-agents-automation:renovate-base.json5" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.github/workflows/changelog.yml: -------------------------------------------------------------------------------- 1 | # This action requires that any PR should touch at 2 | # least one CHANGELOG file. 3 | 4 | name: changelog 5 | 6 | on: 7 | pull_request: 8 | types: [opened, synchronize, reopened, labeled, unlabeled] 9 | 10 | jobs: 11 | check-changelog: 12 | uses: newrelic/k8s-agents-automation/.github/workflows/reusable-changelog.yml@main 13 | -------------------------------------------------------------------------------- /.github/workflows/push_pr.yml: -------------------------------------------------------------------------------- 1 | name: Lint, Build, E2E Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | - renovate/** 9 | release: 10 | types: [published] 11 | pull_request: 12 | workflow_dispatch: 13 | 14 | jobs: 15 | build: 16 | name: Build integration for 17 | runs-on: ubuntu-latest 18 | strategy: 19 | matrix: 20 | goos: [ linux ] 21 | goarch: [ amd64, arm64, arm ] 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: actions/setup-go@v5 25 | with: 26 | go-version-file: 'go.mod' 27 | - name: Build integration 28 | env: 29 | GOOS: ${{ matrix.goos }} 30 | GOARCH: ${{ matrix.goarch }} 31 | run: | 32 | make compile 33 | 34 | chart-lint: 35 | name: Helm chart Lint 36 | runs-on: ubuntu-24.04 37 | timeout-minutes: 10 38 | strategy: 39 | max-parallel: 5 40 | matrix: 41 | kubernetes-version: ["v1.32.0", "v1.31.0", "v1.30.0", "v1.29.5", "v1.28.3"] 42 | steps: 43 | - uses: actions/checkout@v4 44 | with: 45 | fetch-depth: 0 46 | - uses: helm/chart-testing-action@v2.7.0 47 | 48 | - name: Lint charts 49 | run: ct --config .github/ct.yaml lint --debug 50 | 51 | - name: Check for changed installable charts 52 | id: list-changed 53 | run: | 54 | changed=$(ct --config .github/ct.yaml list-changed) 55 | if [[ -n "$changed" ]]; then 56 | echo "::set-output name=changed::true" 57 | fi 58 | - name: Run helm unit tests 59 | if: steps.list-changed.outputs.changed == 'true' 60 | run: | 61 | helm plugin install https://github.com/helm-unittest/helm-unittest 62 | for chart in $(ct --config .github/ct.yaml list-changed); do 63 | if [ -d "$chart/tests/" ]; then 64 | helm unittest $chart 65 | else 66 | echo "No unit tests found for $chart" 67 | fi 68 | done 69 | - name: Setup Minikube 70 | uses: manusa/actions-setup-minikube@v2.14.0 71 | if: steps.list-changed.outputs.changed == 'true' 72 | with: 73 | minikube version: v1.36.0 74 | kubernetes version: ${{ matrix.kubernetes-version }} 75 | github token: ${{ secrets.GITHUB_TOKEN }} 76 | - uses: actions/setup-go@v5 77 | if: steps.list-changed.outputs.changed == 'true' 78 | with: 79 | go-version-file: 'go.mod' 80 | - name: Create image for chart testing 81 | if: steps.list-changed.outputs.changed == 'true' 82 | run: | 83 | GOOS=linux GOARCH=amd64 make compile # Set GOOS and GOARCH explicitly since Dockerfile expects them in the binary name 84 | DOCKER_BUILDKIT=1 docker build -t e2e/metadata-injection:test . 85 | minikube image load e2e/metadata-injection:test 86 | kubectl create ns ct 87 | - name: Test install charts 88 | if: steps.list-changed.outputs.changed == 'true' 89 | run: ct install --namespace ct --config .github/ct.yaml --debug 90 | - name: Test upgrade charts 91 | if: steps.list-changed.outputs.changed == 'true' 92 | run: ct install --namespace ct --config .github/ct.yaml --debug --upgrade 93 | 94 | test: 95 | name: Unit tests 96 | needs: [ build ] 97 | runs-on: ubuntu-latest 98 | steps: 99 | - uses: actions/checkout@v4 100 | - uses: actions/setup-go@v5 101 | with: 102 | go-version-file: 'go.mod' 103 | - name: Run unit tests 104 | run: make test 105 | - name: Upload coverage to Codecov 106 | uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 107 | with: 108 | fail_ci_if_error: false 109 | env: 110 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 111 | 112 | static-analysis: 113 | name: Static analysis and linting 114 | runs-on: ubuntu-latest 115 | steps: 116 | - uses: actions/checkout@v4 117 | - uses: actions/setup-go@v5 118 | with: 119 | go-version-file: 'go.mod' 120 | - uses: newrelic/newrelic-infra-checkers@v1 121 | # - name: Semgrep 122 | # uses: returntocorp/semgrep-action@v1 123 | # with: 124 | # auditOn: push 125 | - name: golangci-lint 126 | uses: golangci/golangci-lint-action@v8 127 | continue-on-error: ${{ github.event_name != 'pull_request' }} 128 | with: 129 | only-new-issues: true 130 | 131 | e2e-kubernetes: 132 | name: Kubernetes E2E tests 133 | needs: [ test ] 134 | runs-on: ubuntu-24.04 135 | env: 136 | E2E_KUBERNETES_VERSION: ${{ matrix.k8s-version }} 137 | DOCKER_BUILDKIT: '1' # Setting DOCKER_BUILDKIT=1 ensures TARGETOS and TARGETARCH are populated 138 | strategy: 139 | fail-fast: false 140 | max-parallel: 5 141 | matrix: 142 | k8s-version: ["v1.32.0", "v1.31.0", "v1.30.0", "v1.29.5", "v1.28.3"] 143 | cri: [ containerd ] 144 | steps: 145 | - uses: actions/checkout@v4 146 | - uses: actions/setup-go@v5 147 | with: 148 | go-version-file: 'go.mod' 149 | - run: make e2e-test 150 | 151 | notify-failure: 152 | if: ${{ always() && failure() }} 153 | needs: [ e2e-kubernetes ] 154 | runs-on: ubuntu-latest 155 | steps: 156 | - name: Notify failure via Slack 157 | uses: archive/github-actions-slack@c643e5093620d65506466f2c9b317d5d29a5e517 # v2.10.1 158 | with: 159 | slack-bot-user-oauth-access-token: ${{ secrets.slack_token }} 160 | slack-channel: ${{ secrets.slack_channel }} 161 | slack-text: "❌ `${{ env.ORIGINAL_REPO_NAME }}`: <${{ github.server_url }}/${{ env.ORIGINAL_REPO_NAME }}/actions/runs/${{ github.run_id }}|'Kubernetes E2E tests' failed>." -------------------------------------------------------------------------------- /.github/workflows/release-chart.yml: -------------------------------------------------------------------------------- 1 | name: Release k8s-metadata-injection chart 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | release-chart: 9 | permissions: 10 | contents: write 11 | uses: newrelic/k8s-agents-automation/.github/workflows/reusable-release-chart.yml@main 12 | secrets: 13 | gh_token: "${{ secrets.GITHUB_TOKEN }}" 14 | slack_channel: ${{ secrets.K8S_AGENTS_SLACK_CHANNEL }} 15 | slack_token: ${{ secrets.K8S_AGENTS_SLACK_TOKEN }} 16 | -------------------------------------------------------------------------------- /.github/workflows/release-integration.yml: -------------------------------------------------------------------------------- 1 | name: Pre-release and Release pipeline 2 | 3 | on: 4 | release: 5 | types: [prereleased, released] 6 | tags: 7 | - 'v*' 8 | workflow_dispatch: 9 | 10 | jobs: 11 | release-integration: 12 | permissions: 13 | contents: write 14 | pull-requests: write 15 | uses: newrelic/k8s-agents-automation/.github/workflows/reusable-release-integration.yml@main 16 | with: 17 | repo_name: k8s-metadata-injection 18 | artifact_path: bin/ 19 | docker_image_name: newrelic/k8s-metadata-injection 20 | chart_directory: charts/nri-metadata-injection 21 | secrets: 22 | dockerhub_username: ${{ secrets.K8S_AGENTS_DOCKERHUB_USERNAME }} 23 | dockerhub_token: ${{ secrets.K8S_AGENTS_DOCKERHUB_TOKEN }} 24 | bot_token: ${{ secrets.K8S_AGENTS_BOT_TOKEN }} 25 | slack_channel: ${{ secrets.K8S_AGENTS_SLACK_CHANNEL }} 26 | slack_token: ${{ secrets.K8S_AGENTS_SLACK_TOKEN }} 27 | -------------------------------------------------------------------------------- /.github/workflows/repolinter.yml: -------------------------------------------------------------------------------- 1 | # NOTE: This file should always be named `repolinter.yml` to allow 2 | # workflow_dispatch to work properly 3 | name: Repolinter Action 4 | 5 | # NOTE: This workflow will ONLY check the default branch! 6 | # Currently there is no elegant way to specify the default 7 | # branch in the event filtering, so branches are instead 8 | # filtered in the "Test Default Branch" step. 9 | on: [push, workflow_dispatch] 10 | 11 | jobs: 12 | repolint: 13 | name: Run Repolinter 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Test Default Branch 17 | id: default-branch 18 | uses: actions/github-script@v7.0.1 19 | with: 20 | script: | 21 | const data = await github.rest.repos.get(context.repo) 22 | return data.data && data.data.default_branch === context.ref.split('/').slice(-1)[0] 23 | - name: Checkout Self 24 | if: ${{ steps.default-branch.outputs.result == 'true' }} 25 | uses: actions/checkout@v4 26 | - name: Run Repolinter 27 | if: ${{ steps.default-branch.outputs.result == 'true' }} 28 | uses: newrelic/repolinter-action@v1 29 | with: 30 | config_url: https://raw.githubusercontent.com/newrelic/.github/main/repolinter-rulesets/community-plus.yml 31 | output_type: issue 32 | -------------------------------------------------------------------------------- /.github/workflows/security.yaml: -------------------------------------------------------------------------------- 1 | name: Security Scan 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | - renovate/** 9 | pull_request: 10 | schedule: 11 | - cron: "0 3 * * *" 12 | 13 | jobs: 14 | trivy: 15 | permissions: 16 | contents: read # for actions/checkout to fetch code 17 | security-events: write # for github/codeql-action/upload-sarif to upload SARIF results 18 | uses: newrelic/k8s-agents-automation/.github/workflows/reusable-security.yaml@main 19 | secrets: 20 | slack_channel: ${{ secrets.K8S_AGENTS_SLACK_CHANNEL }} 21 | slack_token: ${{ secrets.K8S_AGENTS_SLACK_TOKEN }} 22 | -------------------------------------------------------------------------------- /.github/workflows/trigger-release.yml: -------------------------------------------------------------------------------- 1 | name: Trigger release creation 2 | 3 | # This workflow triggers a release creation with changelog and the release notes created by the release toolkit. 4 | # This workflow should be triggered merely from the default branch. 5 | # For more details about how to release follow https://github.com/newrelic/coreint-automation/blob/main/docs/release_runbook.md 6 | 7 | on: 8 | workflow_dispatch: 9 | schedule: 10 | - cron: "0 12 * * 1" # Monday at 12pm UTC or 5am PT 11 | 12 | jobs: 13 | trigger-release: 14 | uses: newrelic/k8s-agents-automation/.github/workflows/reusable-trigger-release.yml@main 15 | with: 16 | bot_email: '${{ vars.K8S_AGENTS_BOT_EMAIL }}' 17 | bot_name: '${{ vars.K8S_AGENTS_BOT_NAME }}' 18 | secrets: 19 | bot_token: ${{ secrets.K8S_AGENTS_BOT_TOKEN }} 20 | slack_channel: ${{ secrets.K8S_AGENTS_SLACK_CHANNEL }} 21 | slack_token: ${{ secrets.K8S_AGENTS_SLACK_TOKEN }} 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | deploy/local.* 2 | bin 3 | 4 | # Downloaded chart dependencies 5 | **/charts/*.tgz 6 | 7 | # Release toolkit 8 | CHANGELOG.partial.md 9 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | default: none 4 | enable: 5 | - asciicheck 6 | - bidichk 7 | - bodyclose 8 | - containedctx 9 | - contextcheck 10 | - cyclop 11 | - dogsled 12 | - dupl 13 | - durationcheck 14 | - err113 15 | - errcheck 16 | - errname 17 | - errorlint 18 | - exhaustive 19 | - forbidigo 20 | - forcetypeassert 21 | - funlen 22 | - gochecknoglobals 23 | - gochecknoinits 24 | - gocognit 25 | - goconst 26 | - gocritic 27 | - gocyclo 28 | - godot 29 | - goheader 30 | - gomodguard 31 | - goprintffuncname 32 | - gosec 33 | - govet 34 | - grouper 35 | - ineffassign 36 | - ireturn 37 | - maintidx 38 | - makezero 39 | - misspell 40 | - mnd 41 | - nestif 42 | - nilerr 43 | - nilnil 44 | - noctx 45 | - nolintlint 46 | - nonamedreturns 47 | - nosprintfhostport 48 | - paralleltest 49 | - prealloc 50 | - predeclared 51 | - promlinter 52 | - revive 53 | - rowserrcheck 54 | - sqlclosecheck 55 | - staticcheck 56 | - thelper 57 | - tparallel 58 | - unconvert 59 | - unparam 60 | - unused 61 | - wastedassign 62 | - whitespace 63 | - wrapcheck 64 | settings: 65 | dupl: 66 | threshold: 100 67 | funlen: 68 | lines: 100 69 | statements: 50 70 | gocyclo: 71 | min-complexity: 10 72 | govet: 73 | settings: 74 | printf: 75 | funcs: 76 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof 77 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf 78 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf 79 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf 80 | misspell: 81 | locale: US 82 | mnd: 83 | checks: 84 | - argument 85 | - case 86 | - condition 87 | - return 88 | nolintlint: 89 | require-explanation: false 90 | require-specific: true 91 | allow-unused: false 92 | exclusions: 93 | generated: lax 94 | presets: 95 | - comments 96 | - common-false-positives 97 | - legacy 98 | - std-error-handling 99 | rules: 100 | - linters: 101 | - paralleltest 102 | text: does not use range value in test Run 103 | paths: 104 | - third_party$ 105 | - builtin$ 106 | - examples$ 107 | formatters: 108 | enable: 109 | - gofmt 110 | - gofumpt 111 | - goimports 112 | exclusions: 113 | generated: lax 114 | paths: 115 | - third_party$ 116 | - builtin$ 117 | - examples$ 118 | -------------------------------------------------------------------------------- /.trivyignore: -------------------------------------------------------------------------------- 1 | # We are running the 2.16.0 version of github.com/emicklei/go-restful that had the fix backported, but trivy still points it out as false-positive 2 | # This is going to be fixed by 2.15 of the kubernetes client go, they decided not to backport the fix since they are not using the impacted feature. 3 | CVE-2022-1996 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## Unreleased 9 | 10 | ## v1.34.1 - 2025-06-02 11 | 12 | ### ⛓️ Dependencies 13 | - Updated alpine to v3.22.0 14 | 15 | ## v1.34.0 - 2025-05-19 16 | 17 | ### 🚀 Enhancements 18 | - Pass container security context into admission webhooks @kondracek-nr [#619](https://github.com/newrelic/k8s-metadata-injection/pull/619) 19 | 20 | ### ⛓️ Dependencies 21 | - Updated golang.org/x/crypto to v0.38.0 22 | - Updated go to v1.24.3 23 | 24 | ## v1.33.1 - 2025-05-05 25 | 26 | ### ⛓️ Dependencies 27 | - Updated kubernetes packages to v0.33.0 28 | 29 | ## v1.33.0 - 2025-04-28 30 | 31 | ### ⛓️ Dependencies 32 | - Upgraded golang.org/x/net from 0.36.0 to 0.38.0 33 | 34 | ## v1.32.2 - 2025-04-14 35 | 36 | ### ⛓️ Dependencies 37 | - Updated go to v1.24.2 38 | - Updated golang.org/x/crypto to v0.37.0 39 | 40 | ## v1.32.1 - 2025-04-07 41 | 42 | ### ⛓️ Dependencies 43 | - Updated github.com/fsnotify/fsnotify to v1.9.0 - [Changelog 🔗](https://github.com/fsnotify/fsnotify/releases/tag/v1.9.0) 44 | 45 | ## v1.32.0 - 2025-03-24 46 | 47 | ### 🚀 Enhancements 48 | - Add v1.32 support and drop support for v1.27 @kpattaswamy [#602](https://github.com/newrelic/k8s-metadata-injection/pull/602) 49 | 50 | ### ⛓️ Dependencies 51 | - Updated kubernetes packages to v0.32.3 52 | - Upgraded golang.org/x/net from 0.33.0 to 0.36.0 53 | 54 | ## v1.31.3 - 2025-03-10 55 | 56 | ### ⛓️ Dependencies 57 | - Updated golang.org/x/crypto to v0.36.0 58 | 59 | ## v1.31.2 - 2025-02-17 60 | 61 | ### ⛓️ Dependencies 62 | - Updated kubernetes packages to v0.32.1 63 | - Updated alpine to v3.21.3 64 | 65 | ## v1.31.1 - 2025-01-27 66 | 67 | ### ⛓️ Dependencies 68 | - Updated go to v1.23.5 69 | 70 | ## v1.31.0 - 2025-01-20 71 | 72 | ### ⛓️ Dependencies 73 | - Updated golang.org/x/crypto to v0.32.0 74 | - Upgraded golang.org/x/net from 0.30.0 to 0.33.0 75 | 76 | ## v1.30.5 - 2025-01-13 77 | 78 | ### ⛓️ Dependencies 79 | - Updated alpine to v3.21.2 80 | 81 | ## v1.30.4 - 2024-12-23 82 | 83 | ### ⛓️ Dependencies 84 | - Updated kubernetes packages to v0.32.0 85 | - Updated go to v1.23.4 86 | - Updated golang.org/x/crypto to v0.31.0 87 | 88 | ## v1.30.3 - 2024-12-09 89 | 90 | ### ⛓️ Dependencies 91 | - Updated alpine to v3.21.0 92 | 93 | ## v1.30.2 - 2024-11-18 94 | 95 | ### ⛓️ Dependencies 96 | - Updated go to v1.23.3 97 | - Updated golang.org/x/crypto to v0.29.0 98 | 99 | ## v1.30.1 - 2024-11-04 100 | 101 | ### ⛓️ Dependencies 102 | - Updated kubernetes packages to v0.31.2 103 | - Updated github.com/fsnotify/fsnotify to v1.8.0 - [Changelog 🔗](https://github.com/fsnotify/fsnotify/releases/tag/v1.8.0) 104 | 105 | ## v1.30.0 - 2024-10-28 106 | 107 | ### 🛡️ Security notices 108 | - Update kube-webhook-certgen to 1.4.3 to patch cve 109 | 110 | ### 🚀 Enhancements 111 | - Add 1.31 support and drop 1.26 @zeitlerc [#574](https://github.com/newrelic/k8s-metadata-injection/pull/574) 112 | 113 | ### ⛓️ Dependencies 114 | - Updated golang.org/x/crypto to v0.28.0 115 | 116 | ## v1.29.2 - 2024-10-07 117 | 118 | ### ⛓️ Dependencies 119 | - Updated go to v1.23.2 120 | - Updated kubernetes packages to v0.31.1 121 | 122 | ## v1.29.1 - 2024-09-09 123 | 124 | ### ⛓️ Dependencies 125 | - Updated golang.org/x/crypto to v0.27.0 126 | - Updated alpine to v3.20.3 127 | 128 | ## v1.29.0 - 2024-09-02 129 | 130 | ### 🚀 Enhancements 131 | - Avoid Using PSP in k8s >= 1.25 @xqi-nr [#529](https://github.com/newrelic/k8s-metadata-injection/pull/529) 132 | 133 | ### ⛓️ Dependencies 134 | - Updated go to v1.23.0 135 | - Updated kubernetes packages to v0.31.0 136 | 137 | ## v1.28.4 - 2024-08-12 138 | 139 | ### ⛓️ Dependencies 140 | - Updated golang.org/x/crypto to v0.26.0 141 | 142 | ## v1.28.3 - 2024-07-29 143 | 144 | ### ⛓️ Dependencies 145 | - Updated alpine to v3.20.2 146 | - Updated kubernetes packages to v0.30.3 147 | 148 | ## v1.28.2 - 2024-07-22 149 | 150 | ### ⛓️ Dependencies 151 | - Updated golang.org/x/crypto to v0.25.0 152 | 153 | ## v1.28.1 - 2024-07-08 154 | 155 | ### ⛓️ Dependencies 156 | - Updated kubernetes packages to v0.30.2 157 | 158 | ## v1.28.0 - 2024-06-24 159 | 160 | ### 🚀 Enhancements 161 | - Add 1.29 and 1.30 support and drop 1.25 and 1.24 @dbudziwojskiNR [#551](https://github.com/newrelic/k8s-metadata-injection/pull/551) 162 | 163 | ### ⛓️ Dependencies 164 | - Updated alpine to v3.20.1 165 | 166 | ## v1.27.4 - 2024-06-17 167 | 168 | ### ⛓️ Dependencies 169 | - Updated go to v1.22.4 170 | - Updated golang.org/x/crypto to v0.24.0 171 | 172 | ## v1.27.3 - 2024-06-10 173 | 174 | ### ⛓️ Dependencies 175 | - Updated go to v1.22.3 176 | 177 | ## v1.27.2 - 2024-05-27 178 | 179 | ### ⛓️ Dependencies 180 | - Updated alpine to v3.20.0 181 | 182 | ## v1.27.1 - 2024-05-13 183 | 184 | ### ⛓️ Dependencies 185 | - Updated golang.org/x/crypto to v0.23.0 186 | 187 | ## v1.27.0 - 2024-04-29 188 | 189 | ### ⛓️ Dependencies 190 | - Upgraded golang.org/x/net from 0.21.0 to 0.23.0 191 | 192 | ## v1.26.4 - 2024-04-15 193 | 194 | ### ⛓️ Dependencies 195 | - Updated golang.org/x/crypto to v0.22.0 196 | 197 | ## v1.26.3 - 2024-03-25 198 | 199 | ### ⛓️ Dependencies 200 | - Updated kubernetes packages to v0.29.3 201 | 202 | ## v1.26.2 - 2024-03-11 203 | 204 | ### ⛓️ Dependencies 205 | - Updated golang.org/x/crypto to v0.21.0 206 | 207 | ## v1.26.1 - 2024-03-04 208 | 209 | ### ⛓️ Dependencies 210 | - Updated kubernetes packages to v0.29.2 211 | 212 | ## v1.26.0 - 2024-02-26 213 | 214 | ### 🚀 Enhancements 215 | - Add linux node selector @dbudziwojskiNR [#523](https://github.com/newrelic/k8s-metadata-injection/pull/523) 216 | 217 | ### ⛓️ Dependencies 218 | - Updated go.uber.org/zap to v1.27.0 219 | 220 | ## v1.25.1 - 2024-02-19 221 | 222 | ### ⛓️ Dependencies 223 | - Updated golang.org/x/crypto to v0.19.0 224 | 225 | ## v1.25.0 - 2024-02-05 226 | 227 | ### 🚀 Enhancements 228 | - Add Codecov @dbudziwojskiNR [#513](https://github.com/newrelic/k8s-metadata-injection/pull/513) 229 | 230 | ## v1.24.2 - 2024-01-29 231 | 232 | ### ⛓️ Dependencies 233 | - Updated kubernetes packages to v0.29.1 234 | - Updated alpine to v3.19.1 235 | 236 | ## v1.24.1 - 2024-01-22 237 | 238 | ### ⛓️ Dependencies 239 | - Updated go to v1.21.6 240 | 241 | ## v1.24.0 - 2024-01-15 242 | 243 | ### 🚀 Enhancements 244 | - Trigger release creation by @juanjjaramillo [#506](https://github.com/newrelic/k8s-metadata-injection/pull/506) 245 | - Remove reusable workflows by @juanjjaramillo [#491](https://github.com/newrelic/k8s-metadata-injection/pull/491) 246 | 247 | ## v1.23.2 - 2024-01-08 248 | 249 | ### ⛓️ Dependencies 250 | - Updated kubernetes packages to v0.29.0 251 | - Updated golang.org/x/crypto to v0.18.0 252 | 253 | ## v1.23.1 - 2023-12-25 254 | 255 | ### 🚀 Enhancements 256 | - Update e2e testing workflow to also run on release in [#485](https://github.com/newrelic/k8s-metadata-injection/pull/485) 257 | 258 | ### ⛓️ Dependencies 259 | - Updated kubernetes packages to v0.28.4 260 | - Updated go to v1.21.5 261 | - Updated alpine to v3.19.0 262 | 263 | ## v1.23.0 - 2023-12-06 264 | 265 | ### 🚀 Enhancements 266 | - Update reusable workflow dependency by @juanjjaramillo [#490](https://github.com/newrelic/k8s-metadata-injection/pull/490) 267 | - Reusable release workflow now provides a mechanism for opting out of helm chart updates [#488](https://github.com/newrelic/k8s-metadata-injection/pull/488) 268 | 269 | ### ⛓️ Dependencies 270 | - Updated golang.org/x/crypto to v0.16.0 271 | - Updated alpine to v3.18.5 272 | 273 | ## v1.22.1 - 2023-11-16 274 | 275 | ### ⛓️ Dependencies 276 | - Updated golang.org/x/crypto to v0.15.0 277 | 278 | ## v1.22.0 - 2023-11-13 279 | 280 | ### 🚀 Enhancements 281 | - Update k8s version in e2e tests by @svetlanabrennan in [#459](https://github.com/newrelic/k8s-metadata-injection/pull/459) 282 | 283 | ## v1.21.0 - 2023-11-13 284 | 285 | ### 🚀 Enhancements 286 | - Replace k8s v1.28.0-rc.1 with k8s 1.28.3 support by @svetlanabrennan in [#458](https://github.com/newrelic/k8s-metadata-injection/pull/458) 287 | 288 | ## v1.20.0 - 2023-11-06 289 | 290 | ### 🛡️ Security notices 291 | - Pin Slack notification action to a hash, not to a tag by @juanjjaramillo in [#447](https://github.com/newrelic/k8s-metadata-injection/pull/447) 292 | 293 | ## v1.19.0 - 2023-10-30 294 | 295 | ### 🚀 Enhancements 296 | - Remove 1.23 support by @svetlanabrennan in [#441](https://github.com/newrelic/k8s-metadata-injection/pull/441) 297 | - Add k8s 1.28.0-rc.1 support by @svetlanabrennan in [#443](https://github.com/newrelic/k8s-metadata-injection/pull/443) 298 | - Upload sarif when running periodically or pushing to main by @juanjaramillo in [#444](https://github.com/newrelic/k8s-metadata-injection/pull/444) 299 | - Improve Trivy scan by using Docker image by @juanjjaramillo in [#446](https://github.com/newrelic/k8s-metadata-injection/pull/446) 300 | 301 | ## v1.18.4 - 2023-10-23 302 | 303 | ### 🐞 Bug fixes 304 | - Trivy scans should only run on the 'Security' workflow by juanjjaramillo in [#436](https://github.com/newrelic/k8s-metadata-injection/pull/436) 305 | 306 | ### ⛓️ Dependencies 307 | - Updated kubernetes packages to v0.28.3 308 | - Updated github.com/fsnotify/fsnotify to v1.7.0 - [Changelog 🔗](https://github.com/fsnotify/fsnotify/releases/tag/v1.7.0) 309 | 310 | ## v1.18.3 - 2023-10-16 311 | 312 | ### 🐞 Bug fixes 313 | - Address CVE-2023-44487 and CVE-2023-39325 by juanjjaramillo in [#434](https://github.com/newrelic/k8s-metadata-injection/pull/434) 314 | 315 | ## v1.18.2 - 2023-10-09 316 | 317 | ### ⛓️ Dependencies 318 | - Updated golang.org/x/crypto to v0.14.0 319 | 320 | ## v1.18.1 - 2023-10-02 321 | 322 | ### 🐞 Bug fixes 323 | - Fix release workflow to include build-time metadata on release image by juanjjaramillo in [#425](https://github.com/newrelic/k8s-metadata-injection/pull/425) 324 | 325 | ## v1.18.0 - 2023-09-29 326 | 327 | ### 🚀 Enhancements 328 | - Improve readability of `release-integration-reusable.yml` by @juanjjaramillo in [#422](https://github.com/newrelic/k8s-metadata-injection/pull/422) 329 | 330 | ## v1.17.0 - 2023-09-29 331 | 332 | ### 🚀 Enhancements 333 | - Make explicit that we are only using a single file by @juanjjaramillo in [#416](https://github.com/newrelic/k8s-metadata-injection/pull/416) 334 | 335 | ### 🐞 Bug fixes 336 | - Fix action to fetch `version-update.go` by @juanjjaramillo in [#420](https://github.com/newrelic/k8s-metadata-injection/pull/420) 337 | - Add quotation to variables to handle spaces by @juanjjaramillo in [#417](https://github.com/newrelic/k8s-metadata-injection/pull/417) 338 | 339 | ### ⛓️ Dependencies 340 | - Updated alpine to v3.18.4 341 | 342 | ## v1.16.1 - 2023-09-26 343 | 344 | ### ⛓️ Dependencies 345 | - Updated go.uber.org/zap to v1.26.0 346 | 347 | ## v1.16.0 - 2023-09-21 348 | 349 | ### 🚀 Enhancements 350 | - update contributing.md docs by @svetlanabrennan in [#389](https://github.com/newrelic/k8s-metadata-injection/pull/389) 351 | 352 | ## v1.15.2 - 2023-09-20 353 | 354 | ### ⛓️ Dependencies 355 | - Updated go.uber.org/zap to v1.26.0 356 | 357 | ## v1.15.1 - 2023-09-18 358 | 359 | ### ⛓️ Dependencies 360 | - Updated kubernetes packages to v0.28.2 361 | - Updated go to 1.21 362 | - Updated golang.org/x/crypto to v0.13.0 363 | 364 | ## v1.15.0 - 2023-09-11 365 | 366 | ### 🚀 Enhancements 367 | - Update K8s Versions in E2E Tests by @xqi-nr in [#369](https://github.com/newrelic/k8s-metadata-injection/pull/369) 368 | 369 | ## v1.14.1 - 2023-09-04 370 | 371 | ### ⛓️ Dependencies 372 | - Updated kubernetes packages to v0.28.1 373 | 374 | ## v1.14.0 - 2023-08-31 375 | 376 | ### 🚀 Enhancements 377 | - Remove old maintainers @svetlanabrennan [#355](https://github.com/newrelic/k8s-metadata-injection/pull/355) 378 | 379 | ## v1.13.0 - 2023-08-28 380 | 381 | ### 🚀 Enhancements 382 | - Define GitHub bot name and email @juanjjaramillo [#343](https://github.com/newrelic/k8s-metadata-injection/pull/343) 383 | 384 | ### ⛓️ Dependencies 385 | - Updated alpine to v3.18.3 386 | 387 | ## v1.12.0 - 2023-08-23 388 | 389 | ### 🛡️ Security notices 390 | - Meet internal security standards @juanjjaramillo [#334](https://github.com/newrelic/k8s-metadata-injection/pull/334) 391 | 392 | ## 1.11.0 393 | ## What's Changed 394 | - Add configuration of certmanager durations @cdobbyn [#323](https://github.com/newrelic/k8s-metadata-injection/pull/323) 395 | - Add changelog workflow @svetlanabrennan [#316](https://github.com/newrelic/k8s-metadata-injection/pull/316) 396 | - Update code owners @juanjjaramillo [#318](https://github.com/newrelic/k8s-metadata-injection/pull/318) 397 | - Add pull request template @svetlanabrennan [#317](https://github.com/newrelic/k8s-metadata-injection/pull/317) 398 | - Add More Logs for NEW_RELIC_METADATA_KUBERNETES_CLUSTER_NAME Injection @xqi-nr [#325](https://github.com/newrelic/k8s-metadata-injection/pull/325) 399 | 400 | **Full Changelog**: https://github.com/newrelic/k8s-metadata-injection/compare/v1.10.2...v1.11.0 401 | 402 | ## 1.10.2 403 | ## What's Changed 404 | * Update CHANGELOG.md by @juanjjaramillo in https://github.com/newrelic/k8s-metadata-injection/pull/302 405 | * Bump versions by @juanjjaramillo in https://github.com/newrelic/k8s-metadata-injection/pull/303 406 | * chore(deps): bump aquasecurity/trivy-action from 0.10.0 to 0.11.2 by @dependabot in https://github.com/newrelic/k8s-metadata-injection/pull/304 407 | * chore(deps): bump alpine from 3.18.0 to 3.18.2 by @dependabot in https://github.com/newrelic/k8s-metadata-injection/pull/305 408 | * chore(deps): bump k8s.io/apimachinery from 0.27.2 to 0.27.3 by @dependabot in https://github.com/newrelic/k8s-metadata-injection/pull/306 409 | * chore(deps): bump k8s.io/api from 0.27.2 to 0.27.3 by @dependabot in https://github.com/newrelic/k8s-metadata-injection/pull/307 410 | * upgrade go version by @xqi-nr in https://github.com/newrelic/k8s-metadata-injection/pull/308 411 | 412 | **Full Changelog**: https://github.com/newrelic/k8s-metadata-injection/compare/v1.10.1...v1.10.2 413 | 414 | ## 1.10.1 415 | 416 | ## What's Changed 417 | * Fix helm unittests by @htroisi in https://github.com/newrelic/k8s-metadata-injection/pull/292 418 | * Bump app and chart versions by @juanjjaramillo in https://github.com/newrelic/k8s-metadata-injection/pull/293 419 | * Update Helm unit test reference by @juanjjaramillo in https://github.com/newrelic/k8s-metadata-injection/pull/294 420 | * chore(deps): bump alpine from 3.17.3 to 3.18.0 by @dependabot in https://github.com/newrelic/k8s-metadata-injection/pull/295 421 | * chore(deps): bump k8s.io/api from 0.27.1 to 0.27.2 by @dependabot in https://github.com/newrelic/k8s-metadata-injection/pull/296 422 | * chore(deps): bump github.com/stretchr/testify from 1.8.2 to 1.8.4 by @dependabot in https://github.com/newrelic/k8s-metadata-injection/pull/301 423 | 424 | 425 | **Full Changelog**: https://github.com/newrelic/k8s-metadata-injection/compare/v1.10.0...v1.10.1 426 | 427 | ## 1.10.0 428 | 429 | - Update dependencies 430 | - Update rennovate workflow 431 | - Bump Helm chart version 432 | 433 | ## 1.9.0 434 | 435 | - Update dependencies 436 | - Update chart maintainers 437 | - Add support for Pod annotations in batch job pods (#261) 438 | 439 | ## 1.8.0 440 | 441 | - Updated dependencies 442 | - Fix: Resolve the issue about MutatingWebhookConfiguration being not supported in v1beta1 443 | 444 | ## 1.7.5 445 | 446 | - Updated dependencies 447 | 448 | ## 1.7.4 449 | 450 | - Fix: Update dependencies to address vulnerability issue (#234) 451 | 452 | ## 1.7.3 453 | 454 | - Updated dependencies 455 | - Fix: Re-enable trivy for high vulnerabilities (#202) 456 | 457 | ## 1.7.2 458 | 459 | - Fix: Update transitive dependencies to address trivy vulnerability issue (#164) 460 | 461 | ## 1.7.1 462 | 463 | - Updated dependencies 464 | 465 | ## 1.7.0 466 | 467 | - Updated dependencies 468 | 469 | ## 1.6.0 470 | 471 | - Adds support for Kubernetes 1.22 472 | 473 | ## 1.5.0 474 | 475 | - Dependencies have been updated to their latest versions (#93) 476 | 477 | ## 1.4.0 478 | 479 | - Support multiarch images 480 | 481 | ## 1.3.2 482 | 483 | - Update k8s-webhook-cert-manager to 1.3.2 484 | This new version introduces a fix to extend support to version 1.19.x of Kubernetes 485 | 486 | ## 1.3.1 487 | 488 | - Use Github Actions for releasing 489 | 490 | ## 1.3.0 491 | 492 | - Update k8s-webhook-cert-manager to 1.3.0 493 | - Update to golang version 1.14.6 and alpine 3.12.0 494 | 495 | ## 1.2.0 496 | 497 | - Update deployment apiVersion to apps/v1 498 | - Add kubernetes.io/legacy-unknown signer with approve permission to rbac for 1.18 compatibility 499 | 500 | ## 1.1.4 501 | 502 | - Update k8s-webhook-cert-manager to 1.2.1 503 | 504 | ## 1.1.3 505 | 506 | - Update k8s-webhook-cert-manager to 1.2.0 507 | 508 | ## 1.1.2 509 | 510 | - Update k8s-webhook-cert-manager to 1.1.1 511 | 512 | ## 1.1.1 513 | 514 | - Change default server timeout to 1s 515 | 516 | ## 1.1.0 517 | 518 | ### Added 519 | 520 | - OpenShift support! 521 | 522 | ### Changed 523 | 524 | - Deployment and Service resources are now explicitly assigned to a namespace. 525 | - The webhook server now listens on a non-root port by default: 8443. 526 | 527 | ## 1.0.0 528 | 529 | - Initial version of the webhook. 530 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are always welcome. Before contributing please read the 4 | [code of conduct](./CODE_OF_CONDUCT.md) and [search the issue tracker](issues); your issue may have already been discussed or fixed in `main`. To contribute, 5 | [fork](https://help.github.com/articles/fork-a-repo/) this repository, commit your changes, and [send a Pull Request](https://help.github.com/articles/using-pull-requests/). 6 | 7 | Note that our [code of conduct](./CODE_OF_CONDUCT.md) applies to all platforms and venues related to this project; please follow it in all your interactions with the project and its participants. 8 | 9 | ## Feature Requests 10 | 11 | Feature requests should be submitted in the [Issue tracker](../../issues), with a description of the expected behavior & use case, where they’ll remain closed until sufficient interest, [e.g. :+1: reactions](https://help.github.com/articles/about-discussions-in-issues-and-pull-requests/), has been [shown by the community](../../issues?q=label%3A%22votes+needed%22+sort%3Areactions-%2B1-desc). 12 | Before submitting an Issue, please search for similar ones in the 13 | [closed issues](../../issues?q=is%3Aissue+is%3Aclosed+label%3Aenhancement). 14 | 15 | ## Pull Requests 16 | 17 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a build. 18 | 2. Increase the version numbers in any examples files and the README.md to the new version that this Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 19 | 3. Add an entry as an unordered list to the CHANGELOG under the `Unreleased` section under an L3 header that specifies the type of your PR. If there is no L3 header for your type of PR already in the Unreleased section, add a new L3 header. Include your github handle and a link to your PR in the entry. Here's an example of how it should look: 20 | ```md 21 | ## Unreleased 22 | 23 | ### bugfix 24 | - Fix some bug in some file @yourGithubHandle [#123](linkToThisPR) 25 | ``` 26 | 27 | - Here are the accepted L3 headers (case sensitive) 28 | + `breaking` 29 | + `security` 30 | + `enhancement` 31 | + `bugfix` 32 | + `dependency` 33 | 34 | - Any other headers won't be accepted and the changelog check will fail. 35 | - You can skip the changelog requirement by using the "Skip Changelog" label if your pull request is only updating files related to the CI/CD process or minor doc changes. 36 | 37 | 4. You may merge the Pull Request in once you have the sign-off of one other developers, or if you do not have permission to do that, you may request the other reviewer to merge it for you. 38 | 39 | ## Contributor License Agreement 40 | 41 | Keep in mind that when you submit your Pull Request, you'll need to sign the CLA via the click-through using CLA-Assistant. If you'd like to execute our corporate CLA, or if you have any questions, please drop us an email at opensource@newrelic.com. 42 | 43 | For more information about CLAs, please check out Alex Russell’s excellent post, 44 | [“Why Do I Need to Sign This?”](https://infrequently.org/2008/06/why-do-i-need-to-sign-this/). 45 | 46 | ## Slack 47 | 48 | We host a public Slack with a dedicated channel for contributors and maintainers of open source projects hosted by New Relic. If you are contributing to this project, you're welcome to request access to the #oss-contributors channel in the newrelicusers.slack.com workspace. To request access, please use this [link](https://join.slack.com/t/newrelicusers/shared_invite/zt-1ayj69rzm-~go~Eo1whIQGYnu3qi15ng). 49 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.22.0 2 | 3 | # Set by docker automatically 4 | # If building with `docker build`, make sure to set GOOS/GOARCH explicitly when calling make: 5 | # `make compile GOOS=something GOARCH=something` 6 | # Otherwise the makefile will not append them to the binary name and docker build will fail. 7 | ARG TARGETOS 8 | ARG TARGETARCH 9 | 10 | RUN mkdir /app 11 | WORKDIR /app 12 | 13 | ADD --chmod=755 entrypoint.sh ./ 14 | ADD --chmod=755 bin/k8s-metadata-injection-${TARGETOS}-${TARGETARCH} ./ 15 | RUN mv k8s-metadata-injection-${TARGETOS}-${TARGETARCH} k8s-metadata-injection 16 | 17 | CMD ["/app/entrypoint.sh"] 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [2019] New Relic, Inc 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN_DIR = ./bin 2 | TEST_COVERAGE_DIR := $(BIN_DIR)/test-coverage 3 | BINARY_NAME ?= $(BIN_DIR)/k8s-metadata-injection 4 | DOCKER_IMAGE_NAME ?= newrelic/k8s-metadata-injection 5 | # This default tag is used during e2e test execution in the ci 6 | DOCKER_IMAGE_TAG ?= local-dev 7 | 8 | GOLANGCILINT_VERSION = 1.43.0 9 | 10 | # required for enabling Go modules inside $GOPATH 11 | export GO111MODULE=on 12 | 13 | # GOOS and GOARCH will likely come from env 14 | GOOS ?= 15 | GOARCH ?= 16 | CGO_ENABLED ?= 0 17 | 18 | ifneq ($(strip $(GOOS)), ) 19 | BINARY_NAME := $(BINARY_NAME)-$(GOOS) 20 | endif 21 | 22 | ifneq ($(strip $(GOARCH)), ) 23 | BINARY_NAME := $(BINARY_NAME)-$(GOARCH) 24 | endif 25 | 26 | .PHONY: all 27 | all: build 28 | 29 | .PHONY: build 30 | build: test compile 31 | 32 | compile: 33 | @echo "=== $(INTEGRATION) === [ compile ]: Building $(INTEGRATION)..." 34 | go mod download 35 | CGO_ENABLED=$(CGO_ENABLED) go build -o $(BINARY_NAME) ./cmd/server 36 | 37 | .PHONY: compile-multiarch 38 | compile-multiarch: 39 | $(MAKE) compile GOOS=linux GOARCH=amd64 40 | $(MAKE) compile GOOS=linux GOARCH=arm64 41 | $(MAKE) compile GOOS=linux GOARCH=arm 42 | 43 | .PHONY: build-container 44 | build-container: 45 | docker build -t $(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG) $$DOCKERARGS . 46 | 47 | .PHONY: test 48 | test: 49 | @echo "[test] Running unit tests" 50 | @mkdir -p $(TEST_COVERAGE_DIR) 51 | @go test ./... -count=1 -coverprofile=$(TEST_COVERAGE_DIR)/coverage.out -covermode=count 52 | 53 | .PHONY: e2e-test 54 | e2e-test: 55 | @echo "[test] Running e2e tests" 56 | ./e2e-tests/tests.sh 57 | 58 | .PHONY: benchmark-test 59 | benchmark-test: 60 | @echo "[test] Running benchmark tests" 61 | @go test -run=^Benchmark* -bench . 62 | 63 | # rt-update-changelog runs the release-toolkit run.sh script by piping it into bash to update the CHANGELOG.md. 64 | # It also passes down to the script all the flags added to the make target. To check all the accepted flags, 65 | # see: https://github.com/newrelic/release-toolkit/blob/main/contrib/ohi-release-notes/run.sh 66 | # e.g. `make rt-update-changelog -- -v` 67 | rt-update-changelog: 68 | curl "https://raw.githubusercontent.com/newrelic/release-toolkit/v1/contrib/ohi-release-notes/run.sh" | bash -s -- $(filter-out $@,$(MAKECMDGOALS)) 69 | 70 | 71 | .PHONY: compile rt-update-changelog 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | New Relic Open Source community plus project banner. 2 | 3 | # Kubernetes Metadata injection for New Relic APM agents [![codecov](https://codecov.io/gh/newrelic/k8s-metadata-injection/graph/badge.svg?token=vuX7pJaRnS)](https://codecov.io/gh/newrelic/k8s-metadata-injection) [![Build Status](https://travis-ci.com/newrelic/k8s-metadata-injection.svg?branch=main)](https://travis-ci.com/newrelic/k8s-metadata-injection) [![Go Report Card](https://goreportcard.com/badge/github.com/newrelic/k8s-metadata-injection)](https://goreportcard.com/report/github.com/newrelic/k8s-metadata-injection) 4 | 5 | # Table of contents 6 | 7 | - [Documentation](#documentation) 8 | - [Development](#development) 9 | - [Prerequisites](#prerequisites) 10 | - [Dependency management](#dependency-management) 11 | - [Configuration](#configuration) 12 | - [Run](#run) 13 | - [Tests](#tests) 14 | - [API Documentation](#api-documentation) 15 | - [Performance](#performance) 16 | - [Certificates management](#certificates-management) 17 | - [Automatic](#automatic) 18 | - [Custom](#custom) 19 | - [Contributing](#contributing) 20 | - [License](#license) 21 | - [Release a new version](#release-a-new-version) 22 | 23 | ## Documentation 24 | 25 | If you wish to read higher-level documentation about this project, please, visit the [official documentation site](https://docs.newrelic.com/docs/integrations/kubernetes-integration/metadata-injection/kubernetes-apm-metadata-injection). 26 | 27 | # How does it work? 28 | 29 | New Relic APM agents requires the following environment variables to provide Kubernetes object information in the context of an specific application distributed trace, transaction trace or error trace. 30 | 31 | - `NEW_RELIC_METADATA_KUBERNETES_CLUSTER_NAME` 32 | - `NEW_RELIC_METADATA_KUBERNETES_NODE_NAME` 33 | - `NEW_RELIC_METADATA_KUBERNETES_NAMESPACE_NAME` 34 | - `NEW_RELIC_METADATA_KUBERNETES_DEPLOYMENT_NAME` 35 | - `NEW_RELIC_METADATA_KUBERNETES_POD_NAME` 36 | - `NEW_RELIC_METADATA_KUBERNETES_CONTAINER_NAME` 37 | - `NEW_RELIC_METADATA_KUBERNETES_CONTAINER_IMAGE_NAME` 38 | 39 | These environment variables are automatically injected in the pods using a MutatingAdmissionWebhook provided by this project. 40 | 41 | Please refer to the [official documentation](https://docs.newrelic.com/docs/integrations/kubernetes-integration/metadata-injection/kubernetes-apm-metadata-injection) to learn more about the reasoning behind this project. 42 | 43 | ## Helm chart 44 | 45 | You can install this integration using [`nri-bundle` helm chart](https://github.com/newrelic/helm-charts/tree/master/charts/nri-bundle) located in the 46 | [helm-charts repository](https://github.com/newrelic/helm-charts) or directly from this repository by adding this Helm repository: 47 | 48 | ```shell 49 | helm repo add nri-metadata-injection https://newrelic.github.io/k8s-metadata-injection 50 | helm upgrade --install nri-metadata-injection/nri-metadata-injection -f your-custom-values.yaml 51 | ``` 52 | 53 | For further information of the configuration needed for the chart just read the [chart's README](/charts/nri-metadata-injection/README.md). 54 | 55 | ## Development 56 | 57 | ### Prerequisites 58 | 59 | For the development process [Minikube](https://kubernetes.io/docs/getting-started-guides/minikube) and [Skaffold](https://github.com/GoogleCloudPlatform/skaffold) tools are used. 60 | 61 | - [Install Minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/). 62 | - [Install Skaffold](https://github.com/GoogleCloudPlatform/skaffold#installation). 63 | 64 | Currently the project compiles with **Go 1.11.4**. 65 | 66 | ### Dependency management 67 | 68 | [Go modules](https://github.com/golang/go/wiki/Modules) are used for managing dependencies. This project does not need to be in your GOROOT, if you wish so. 69 | 70 | Currently for K8s libraries it uses version 1.13.1. Only couple of libraries are direct dependencies, the rest are indirect. You need to point all of them to the same K8s version to make sure that everything works as expected. For the moment this process is manual. 71 | 72 | ### Configuration 73 | 74 | - Copy the deployment file `deploy/newrelic-metadata-injection.yaml` to `deploy/local.yaml`. 75 | - Edit the file and set the following value as container image: `internal/k8s-metadata-injector`. 76 | - Make sure that `imagePullPolicy: Always` is not present in the file (otherwise, the image won't be pulled). 77 | 78 | ### Run 79 | 80 | Run `skaffold run`. This will build a docker image, build the webhook server inside it, and finally deploy the webhook server to your Minikube and use the Kubernetes API server to sign its TLS certificate ([see section about certificates](#3-install-the-certificates)). 81 | 82 | To follow the logs, you can run `skaffold run --tail`. To delete the resources created by Skaffold you can run `skaffold delete`. 83 | 84 | If you would like to enable automatic redeploy on changes to the repository, you can run `skaffold dev`. It automatically tails the logs and delete the resources when interrupted (i.e. with a `Ctrl + C`). 85 | 86 | ### Tests 87 | 88 | For running unit tests, use 89 | 90 | ```bash 91 | make test 92 | ``` 93 | 94 | For running benchmark tests, use: 95 | 96 | ```bash 97 | make benchmark-test 98 | ``` 99 | 100 | There are also some basic E2E tests, they are prepared to run using 101 | [Minikube](https://github.com/kubernetes/minikube). To run them, execute: 102 | 103 | ``` bash 104 | make e2e-test 105 | ``` 106 | 107 | The e2e tests make the assumption that you are running on an AMD system so in case the test doesn't generate the needed binary, run the below command. 108 | For instance if you run on an M2 mac arm64 is the target arch but it is not made by default. 109 | 110 | ```bash 111 | make compile-multiarch 112 | ``` 113 | 114 | You can specify against which version of K8s you want to execute the tests: 115 | 116 | ``` bash 117 | E2E_KUBERNETES_VERSION=v1.10.0 E2E_START_MINIKUBE=yes make e2e-test 118 | ``` 119 | 120 | ### API Documentation 121 | 122 | Please use the [Open Api 3.0 spec file](openapi.yaml) as documentation reference. Note that it describes the schema of the requests the webhook server replies to. This schema depends on the currently supported Kubernetes versions. 123 | 124 | You can go to [editor.swagger.io](editor.swagger.io) and paste its contents there to see a rendered version. 125 | 126 | ### Performance 127 | 128 | Please refer to [docs/performance.md](docs/performance.md). 129 | 130 | ## Certificates management 131 | 132 | Admission webhooks are called by the Kubernetes API server and it needs to authenticate the webhooks using TLS. In this project we offer 2 different options of certificate management. 133 | 134 | Either certificate management choice made, the important thing is to have the secret created with the correct name and namespace, and also to have the correct CA bundle in the MutatingWebhookConfiguration resource. As long as this is done the webhook server will be able to pick it up. 135 | 136 | ### Automatic 137 | 138 | Please refer to the [setup instructions in the official documentation](https://docs.newrelic.com/docs/integrations/kubernetes-integration/metadata-injection/kubernetes-apm-metadata-injection#install). 139 | 140 | For the automatic certificate management, the [k8s-webhook-cert-manager](https://github.com/newrelic/k8s-webhook-cert-manager) is used. Feel free to check the repository to know more about it. 141 | 142 | The manifest file at [deploy/job.yaml](./deploy/job.yaml) contains a service account that has the following **cluster** permissions (**RBAC based**) to be capable of automatically manage the certificates: 143 | 144 | - `MutatingWebhookConfiguration` - **get**, **create** and **patch**: to be able to create the webhook and patch its CA bundle. 145 | - `CertificateSigningRequests` - **create**, **get** and **delete**: to be able to sign the certificate required for the webhook server without leaving duplicates. 146 | - `CertificateSigningRequests/Approval` - **update**: to be able to approve CertificateSigningRequests. 147 | - `Secrets` - **create**, **get** and **patch**: to be able to manage the TLS secret used to store the key/cert pair used in the webhook server. 148 | - `ConfigMaps` - **get**: to be able go get the k8s api server's CA bundle, used in the MutatingWebhookConfiguration. 149 | 150 | If you wish to learn more about TLS certificates management inside Kubernetes, check out [the official documentation for Managing TLS Certificates in a Cluster](https://kubernetes.io/docs/tasks/tls/managing-tls-in-a-cluster/#create-a-certificate-signing-request-object-to-send-to-the-kubernetes-api). 151 | 152 | ### Custom 153 | 154 | Otherwise, if you want to use the custom certificate management option you have to create the TLS secret with the signed certificate/key pair and patch the webhook's CA bundle: 155 | 156 | ```bash 157 | $ kubectl create secret tls newrelic-metadata-injection-secret \ 158 | --key=server-key.pem \ 159 | --cert=signed-server-cert.pem \ 160 | --dry-run -o yaml | 161 | kubectl -n default apply -f - 162 | 163 | $ caBundle=$(cat caBundle.pem | base64 | td -d '\n') 164 | $ kubectl patch mutatingwebhookconfiguration newrelic-metadata-injection-cfg --type='json' -p "[{'op': 'replace', 'path': '/webhooks/0/clientConfig/caBundle', 'value':'${caBundle}'}]" 165 | ``` 166 | 167 | ## Release a new version 168 | 169 | - Update the version in `deploy/newrelic-metadata-injection.yaml`. 170 | - Update the version in `WEBHOOK_DOCKER_IMAGE_TAG` in the `Makefile`. 171 | - Create a Github release. 172 | - Launch the `k8s-metadata-injection-release` job in Jenkins. 173 | 174 | ## Support 175 | 176 | Should you need assistance with New Relic products, you are in good hands with several support diagnostic tools and support channels. 177 | 178 | >New Relic offers NRDiag, [a client-side diagnostic utility](https://docs.newrelic.com/docs/using-new-relic/cross-product-functions/troubleshooting/new-relic-diagnostics) that automatically detects common problems with New Relic agents. If NRDiag detects a problem, it suggests troubleshooting steps. NRDiag can also automatically attach troubleshooting data to a New Relic Support ticket. Remove this section if it doesn't apply. 179 | 180 | If the issue has been confirmed as a bug or is a feature request, file a GitHub issue. 181 | 182 | **Support Channels** 183 | 184 | - [New Relic Documentation](https://docs.newrelic.com): Comprehensive guidance for using our platform 185 | - [New Relic Community](https://forum.newrelic.com/t/new-relic-kubernetes-open-source-integration/109093): The best place to engage in troubleshooting questions 186 | - [New Relic Developer](https://developer.newrelic.com/): Resources for building a custom observability applications 187 | - [New Relic University](https://learn.newrelic.com/): A range of online training for New Relic users of every level 188 | - [New Relic Technical Support](https://support.newrelic.com/) 24/7/365 ticketed support. Read more about our [Technical Support Offerings](https://docs.newrelic.com/docs/licenses/license-information/general-usage-licenses/support-plan). 189 | 190 | ## Privacy 191 | 192 | At New Relic we take your privacy and the security of your information seriously, and are committed to protecting your information. We must emphasize the importance of not sharing personal data in public forums, and ask all users to scrub logs and diagnostic information for sensitive information, whether personal, proprietary, or otherwise. 193 | 194 | We define “Personal Data” as any information relating to an identified or identifiable individual, including, for example, your name, phone number, post code or zip code, Device ID, IP address, and email address. 195 | 196 | For more information, review [New Relic’s General Data Privacy Notice](https://newrelic.com/termsandconditions/privacy). 197 | 198 | ## Contribute 199 | 200 | We encourage your contributions to improve this project! Keep in mind that when you submit your pull request, you'll need to sign the CLA via the click-through using CLA-Assistant. You only have to sign the CLA one time per project. 201 | 202 | If you have any questions, or to execute our corporate CLA (which is required if your contribution is on behalf of a company), drop us an email at opensource@newrelic.com. 203 | 204 | **A note about vulnerabilities** 205 | 206 | As noted in our [security policy](../../security/policy), New Relic is committed to the privacy and security of our customers and their data. We believe that providing coordinated disclosure by security researchers and engaging with the security community are important means to achieve our security goals. 207 | 208 | If you believe you have found a security vulnerability in this project or any of New Relic's products or websites, we welcome and greatly appreciate you reporting it to New Relic through [Hackerour bug bounty program](https://docs.newrelic.com/docs/security/security-privacy/information-security/report-security-vulnerabilities/). 209 | 210 | If you would like to contribute to this project, review [these guidelines](./CONTRIBUTING.md). 211 | 212 | To all contributors, we thank you! Without your contribution, this project would not be what it is today. 213 | 214 | ## License 215 | 216 | Kubernetes Metadata injection is licensed under the [Apache 2.0](http://apache.org/licenses/LICENSE-2.0.txt) License. 217 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/.helmignore: -------------------------------------------------------------------------------- 1 | templates/admission-webhooks/job-patch/README.md 2 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/Chart.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: common-library 3 | repository: https://helm-charts.newrelic.com 4 | version: 1.3.1 5 | digest: sha256:cfa7bfb136b9bcfe87e37d3556c3fedecc58f42685c4ce39485da106408b6619 6 | generated: "2025-01-08T01:20:29.871261607Z" 7 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: nri-metadata-injection 3 | description: A Helm chart to deploy the New Relic metadata injection webhook. 4 | home: https://hub.docker.com/r/newrelic/k8s-metadata-injection 5 | icon: https://newrelic.com/assets/newrelic/source/NewRelic-logo-square.svg 6 | sources: 7 | - https://github.com/newrelic/k8s-metadata-injection 8 | - https://github.com/newrelic/k8s-metadata-injection/tree/master/charts/nri-metadata-injection 9 | version: 4.26.1 10 | appVersion: 1.34.1 11 | keywords: 12 | - infrastructure 13 | - newrelic 14 | - monitoring 15 | dependencies: 16 | - name: common-library 17 | version: 1.3.1 18 | repository: "https://helm-charts.newrelic.com" 19 | maintainers: 20 | - name: juanjjaramillo 21 | url: https://github.com/juanjjaramillo 22 | - name: csongnr 23 | url: https://github.com/csongnr 24 | - name: dbudziwojskiNR 25 | url: https://github.com/dbudziwojskiNR 26 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/README.md: -------------------------------------------------------------------------------- 1 | # nri-metadata-injection 2 | 3 | A Helm chart to deploy the New Relic metadata injection webhook. 4 | 5 | **Homepage:** 6 | 7 | # Helm installation 8 | 9 | You can install this chart using [`nri-bundle`](https://github.com/newrelic/helm-charts/tree/master/charts/nri-bundle) located in the 10 | [helm-charts repository](https://github.com/newrelic/helm-charts) or directly from this repository by adding this Helm repository: 11 | 12 | ```shell 13 | helm repo add nri-metadata-injection https://newrelic.github.io/k8s-metadata-injection 14 | helm upgrade --install nri-metadata-injection/nri-metadata-injection -f your-custom-values.yaml 15 | ``` 16 | 17 | ## Source Code 18 | 19 | * 20 | * 21 | 22 | ## Values managed globally 23 | 24 | This chart implements the [New Relic's common Helm library](https://github.com/newrelic/helm-charts/tree/master/library/common-library) which 25 | means that it honors a wide range of defaults and globals common to most New Relic Helm charts. 26 | 27 | Options that can be defined globally include `affinity`, `nodeSelector`, `tolerations`, `proxy` and others. The full list can be found at 28 | [user's guide of the common library](https://github.com/newrelic/helm-charts/blob/master/library/common-library/README.md). 29 | 30 | ## Values 31 | 32 | | Key | Type | Default | Description | 33 | |-----|------|---------|-------------| 34 | | affinity | object | `{}` | Sets pod/node affinities. Can be configured also with `global.affinity` | 35 | | certManager.enabled | bool | `false` | Use cert manager for webhook certs | 36 | | certManager.rootCertificateDuration | string | `"43800h"` | Sets the root certificate duration. Defaults to 43800h (5 years). | 37 | | certManager.webhookCertificateDuration | string | `"8760h"` | Sets certificate duration. Defaults to 8760h (1 year). | 38 | | cluster | string | `""` | Name of the Kubernetes cluster monitored. Can be configured also with `global.cluster` | 39 | | containerSecurityContext | object | `{}` | Sets security context (at container level). Can be configured also with `global.containerSecurityContext` | 40 | | customTLSCertificate | bool | `false` | Use custom tls certificates for the webhook, or let the chart handle it automatically. Ref: https://docs.newrelic.com/docs/integrations/kubernetes-integration/link-your-applications/link-your-applications-kubernetes#configure-injection | 41 | | dnsConfig | object | `{}` | Sets pod's dnsConfig. Can be configured also with `global.dnsConfig` | 42 | | fullnameOverride | string | `""` | Override the full name of the release | 43 | | hostNetwork | bool | false | Sets pod's hostNetwork. Can be configured also with `global.hostNetwork` | 44 | | image | object | See `values.yaml` | Image for the New Relic Metadata Injector | 45 | | image.pullSecrets | list | `[]` | The secrets that are needed to pull images from a custom registry. | 46 | | injectOnlyLabeledNamespaces | bool | `false` | Enable the metadata decoration only for pods living in namespaces labeled with 'newrelic-metadata-injection=enabled'. | 47 | | jobImage | object | See `values.yaml` | Image for creating the needed certificates of this webhook to work | 48 | | jobImage.pullSecrets | list | `[]` | The secrets that are needed to pull images from a custom registry. | 49 | | jobImage.volumeMounts | list | `[]` | Volume mounts to add to the job, you might want to mount tmp if Pod Security Policies Enforce a read-only root. | 50 | | jobImage.volumes | list | `[]` | Volumes to add to the job container | 51 | | labels | object | `{}` | Additional labels for chart objects. Can be configured also with `global.labels` | 52 | | nameOverride | string | `""` | Override the name of the chart | 53 | | nodeSelector | object | `{}` | Sets pod's node selector. Can be configured also with `global.nodeSelector` | 54 | | podAnnotations | object | `{}` | Annotations to be added to all pods created by the integration. | 55 | | podLabels | object | `{}` | Additional labels for chart pods. Can be configured also with `global.podLabels` | 56 | | podSecurityContext | object | `{}` | Sets security context (at pod level). Can be configured also with `global.podSecurityContext` | 57 | | priorityClassName | string | `""` | Sets pod's priorityClassName. Can be configured also with `global.priorityClassName` | 58 | | rbac.pspEnabled | bool | `false` | Whether the chart should create Pod Security Policy objects. | 59 | | replicas | int | `1` | | 60 | | resources | object | 100m/30M -/80M | Image for creating the needed certificates of this webhook to work | 61 | | timeoutSeconds | int | `28` | Webhook timeout Ref: https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#timeouts | 62 | | tolerations | list | `[]` | Sets pod's tolerations to node taints. Can be configured also with `global.tolerations` | 63 | 64 | ## Maintainers 65 | 66 | * [juanjjaramillo](https://github.com/juanjjaramillo) 67 | * [csongnr](https://github.com/csongnr) 68 | * [dbudziwojskiNR](https://github.com/dbudziwojskiNR) 69 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/README.md.gotmpl: -------------------------------------------------------------------------------- 1 | {{ template "chart.header" . }} 2 | {{ template "chart.deprecationWarning" . }} 3 | 4 | {{ template "chart.description" . }} 5 | 6 | {{ template "chart.homepageLine" . }} 7 | 8 | # Helm installation 9 | 10 | You can install this chart using [`nri-bundle`](https://github.com/newrelic/helm-charts/tree/master/charts/nri-bundle) located in the 11 | [helm-charts repository](https://github.com/newrelic/helm-charts) or directly from this repository by adding this Helm repository: 12 | 13 | ```shell 14 | helm repo add nri-metadata-injection https://newrelic.github.io/k8s-metadata-injection 15 | helm upgrade --install nri-metadata-injection/nri-metadata-injection -f your-custom-values.yaml 16 | ``` 17 | 18 | {{ template "chart.sourcesSection" . }} 19 | 20 | ## Values managed globally 21 | 22 | This chart implements the [New Relic's common Helm library](https://github.com/newrelic/helm-charts/tree/master/library/common-library) which 23 | means that it honors a wide range of defaults and globals common to most New Relic Helm charts. 24 | 25 | Options that can be defined globally include `affinity`, `nodeSelector`, `tolerations`, `proxy` and others. The full list can be found at 26 | [user's guide of the common library](https://github.com/newrelic/helm-charts/blob/master/library/common-library/README.md). 27 | 28 | {{ template "chart.valuesSection" . }} 29 | 30 | {{ if .Maintainers }} 31 | ## Maintainers 32 | {{ range .Maintainers }} 33 | {{- if .Name }} 34 | {{- if .Url }} 35 | * [{{ .Name }}]({{ .Url }}) 36 | {{- else }} 37 | * {{ .Name }} 38 | {{- end }} 39 | {{- end }} 40 | {{- end }} 41 | {{- end }} 42 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/ci/test-values.yaml: -------------------------------------------------------------------------------- 1 | cluster: test-cluster 2 | 3 | image: 4 | repository: e2e/metadata-injection 5 | tag: test # Defaults to AppVersion 6 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | Your deployment of the New Relic metadata injection webhook is complete. You can check on the progress of this by running the following command: 2 | 3 | kubectl get deployments -o wide -w --namespace {{ .Release.Namespace }} {{ template "newrelic.common.naming.fullname" . }} 4 | 5 | {{- if .Values.customTLSCertificate }} 6 | You have configure the chart to use a custom tls certificate, make sure to read the 'Manage custom certificates' section of the official docs to find the instructions on how to finish setting up the webhook. 7 | 8 | https://docs.newrelic.com/docs/integrations/kubernetes-integration/link-your-applications/link-your-applications-kubernetes#configure-injection 9 | {{- end }} 10 | 11 | To validate the injection of metadata create a dummy pod containing Busybox by running: 12 | 13 | kubectl create -f https://git.io/vPieo 14 | 15 | Check if New Relic environment variables were injected: 16 | 17 | kubectl exec busybox0 -- env | grep NEW_RELIC_METADATA_KUBERNETES 18 | 19 | NEW_RELIC_METADATA_KUBERNETES_CLUSTER_NAME=fsi 20 | NEW_RELIC_METADATA_KUBERNETES_NODE_NAME=nodea 21 | NEW_RELIC_METADATA_KUBERNETES_NAMESPACE_NAME=default 22 | NEW_RELIC_METADATA_KUBERNETES_POD_NAME=busybox0 23 | NEW_RELIC_METADATA_KUBERNETES_CONTAINER_NAME=busybox 24 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | 3 | {{- /* Allow to change pod defaults dynamically */ -}} 4 | {{- define "nri-metadata-injection.securityContext.pod" -}} 5 | {{- if include "newrelic.common.securityContext.pod" . -}} 6 | {{- include "newrelic.common.securityContext.pod" . -}} 7 | {{- else -}} 8 | fsGroup: 1001 9 | runAsUser: 1001 10 | runAsGroup: 1001 11 | {{- end -}} 12 | {{- end -}} 13 | 14 | {{- /* 15 | Naming helpers 16 | */ -}} 17 | 18 | {{- define "nri-metadata-injection.name.admission" -}} 19 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.name" .) "suffix" "admission") }} 20 | {{- end -}} 21 | 22 | {{- define "nri-metadata-injection.fullname.admission" -}} 23 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "admission") }} 24 | {{- end -}} 25 | 26 | {{- define "nri-metadata-injection.fullname.admission.serviceAccount" -}} 27 | {{- if include "newrelic.common.serviceAccount.create" . -}} 28 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "admission") }} 29 | {{- else -}} 30 | {{ include "newrelic.common.serviceAccount.name" . }} 31 | {{- end -}} 32 | {{- end -}} 33 | 34 | {{- define "nri-metadata-injection.name.admission-create" -}} 35 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.name" .) "suffix" "admission-create") }} 36 | {{- end -}} 37 | 38 | {{- define "nri-metadata-injection.fullname.admission-create" -}} 39 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "admission-create") }} 40 | {{- end -}} 41 | 42 | {{- define "nri-metadata-injection.name.admission-patch" -}} 43 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.name" .) "suffix" "admission-patch") }} 44 | {{- end -}} 45 | 46 | {{- define "nri-metadata-injection.fullname.admission-patch" -}} 47 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "admission-patch") }} 48 | {{- end -}} 49 | 50 | {{- define "nri-metadata-injection.name.self-signed-issuer" -}} 51 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.name" .) "suffix" "self-signed-issuer") }} 52 | {{- end -}} 53 | 54 | {{- define "nri-metadata-injection.fullname.self-signed-issuer" -}} 55 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "self-signed-issuer") }} 56 | {{- end -}} 57 | 58 | {{- define "nri-metadata-injection.name.root-issuer" -}} 59 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.name" .) "suffix" "root-issuer") }} 60 | {{- end -}} 61 | 62 | {{- define "nri-metadata-injection.fullname.root-issuer" -}} 63 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "root-issuer") }} 64 | {{- end -}} 65 | 66 | {{- define "nri-metadata-injection.name.webhook-cert" -}} 67 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.name" .) "suffix" "webhook-cert") }} 68 | {{- end -}} 69 | 70 | {{- define "nri-metadata-injection.fullname.webhook-cert" -}} 71 | {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "webhook-cert") }} 72 | {{- end -}} 73 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/templates/admission-webhooks/job-patch/README.md: -------------------------------------------------------------------------------- 1 | The manifests in this directory are modified version of the manifests coming from 2 | the [kube-prometheus-stack](https://github.com/prometheus-community/helm-charts/tree/f1729dcfd2040660d4f3dcbe3b2f797415990711/charts/kube-prometheus-stack/templates/prometheus-operator/admission-webhooks/job-patch) 3 | Helm chart. 4 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/templates/admission-webhooks/job-patch/clusterrole.yaml: -------------------------------------------------------------------------------- 1 | {{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: {{ include "nri-metadata-injection.fullname.admission" . }} 6 | annotations: 7 | "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade 8 | "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded 9 | labels: 10 | app: {{ include "newrelic.common.naming.name" $ }}-admission 11 | {{- include "newrelic.common.labels" . | nindent 4 }} 12 | rules: 13 | - apiGroups: 14 | - admissionregistration.k8s.io 15 | resources: 16 | - mutatingwebhookconfigurations 17 | verbs: 18 | - get 19 | - update 20 | {{- if .Values.rbac.pspEnabled }} 21 | - apiGroups: ['policy'] 22 | resources: ['podsecuritypolicies'] 23 | verbs: ['use'] 24 | resourceNames: 25 | - {{ include "nri-metadata-injection.fullname.admission" . }} 26 | {{- end }} 27 | {{- end }} 28 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/templates/admission-webhooks/job-patch/clusterrolebinding.yaml: -------------------------------------------------------------------------------- 1 | {{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRoleBinding 4 | metadata: 5 | name: {{ include "nri-metadata-injection.fullname.admission" . }} 6 | annotations: 7 | "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade 8 | "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded 9 | labels: 10 | {{- include "newrelic.common.labels" . | nindent 4 }} 11 | app: {{ include "nri-metadata-injection.name.admission" . }} 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: ClusterRole 15 | name: {{ include "nri-metadata-injection.fullname.admission" . }} 16 | subjects: 17 | - kind: ServiceAccount 18 | name: {{ include "nri-metadata-injection.fullname.admission.serviceAccount" . }} 19 | namespace: {{ .Release.Namespace }} 20 | {{- end }} 21 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/templates/admission-webhooks/job-patch/job-createSecret.yaml: -------------------------------------------------------------------------------- 1 | {{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} 2 | apiVersion: batch/v1 3 | kind: Job 4 | metadata: 5 | name: {{ include "nri-metadata-injection.fullname.admission-create" . }} 6 | namespace: {{ .Release.Namespace }} 7 | annotations: 8 | "helm.sh/hook": pre-install,pre-upgrade 9 | "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded 10 | labels: 11 | app: {{ include "nri-metadata-injection.name.admission-create" . }} 12 | {{- include "newrelic.common.labels" . | nindent 4 }} 13 | spec: 14 | template: 15 | metadata: 16 | name: {{ include "nri-metadata-injection.fullname.admission-create" . }} 17 | {{- if .Values.podAnnotations }} 18 | annotations: 19 | {{- toYaml .Values.podAnnotations | nindent 8 }} 20 | {{- end }} 21 | labels: 22 | app: {{ include "nri-metadata-injection.name.admission-create" . }} 23 | {{- include "newrelic.common.labels" . | nindent 8 }} 24 | spec: 25 | {{- with include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" ( list .Values.jobImage.pullSecrets ) "context" .) }} 26 | imagePullSecrets: 27 | {{- . | nindent 8 -}} 28 | {{- end }} 29 | containers: 30 | - name: create 31 | image: {{ include "newrelic.common.images.image" ( dict "defaultRegistry" "registry.k8s.io" "imageRoot" .Values.jobImage "context" .) }} 32 | imagePullPolicy: {{ .Values.jobImage.pullPolicy }} 33 | {{- with include "newrelic.common.securityContext.container" . }} 34 | securityContext: 35 | {{- . | nindent 12 }} 36 | {{- end }} 37 | args: 38 | - create 39 | - --host={{ include "newrelic.common.naming.fullname" . }},{{ include "newrelic.common.naming.fullname" . }}.{{ .Release.Namespace }}.svc 40 | - --namespace={{ .Release.Namespace }} 41 | - --secret-name={{ include "nri-metadata-injection.fullname.admission" . }} 42 | - --cert-name=tls.crt 43 | - --key-name=tls.key 44 | {{- if .Values.jobImage.volumeMounts }} 45 | volumeMounts: 46 | {{- .Values.jobImage.volumeMounts | toYaml | nindent 10 }} 47 | {{- end }} 48 | {{- if .Values.jobImage.volumes }} 49 | volumes: 50 | {{- .Values.jobImage.volumes | toYaml | nindent 8 }} 51 | {{- end }} 52 | restartPolicy: OnFailure 53 | serviceAccountName: {{ include "nri-metadata-injection.fullname.admission.serviceAccount" . }} 54 | securityContext: 55 | runAsGroup: 2000 56 | runAsNonRoot: true 57 | runAsUser: 2000 58 | nodeSelector: 59 | kubernetes.io/os: linux 60 | {{ include "newrelic.common.nodeSelector" . | nindent 8 }} 61 | {{- if .Values.tolerations }} 62 | tolerations: 63 | {{- toYaml .Values.tolerations | nindent 8 }} 64 | {{- end }} 65 | {{- end }} 66 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/templates/admission-webhooks/job-patch/job-patchWebhook.yaml: -------------------------------------------------------------------------------- 1 | {{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} 2 | apiVersion: batch/v1 3 | kind: Job 4 | metadata: 5 | name: {{ include "nri-metadata-injection.fullname.admission-patch" . }} 6 | namespace: {{ .Release.Namespace }} 7 | annotations: 8 | "helm.sh/hook": post-install,post-upgrade 9 | "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded 10 | labels: 11 | app: {{ include "nri-metadata-injection.name.admission-patch" . }} 12 | {{- include "newrelic.common.labels" . | nindent 4 }} 13 | spec: 14 | template: 15 | metadata: 16 | name: {{ include "nri-metadata-injection.fullname.admission-patch" . }} 17 | {{- if .Values.podAnnotations }} 18 | annotations: 19 | {{- toYaml .Values.podAnnotations | nindent 8 }} 20 | {{- end }} 21 | labels: 22 | app: {{ include "nri-metadata-injection.name.admission-patch" . }} 23 | {{- include "newrelic.common.labels" . | nindent 8 }} 24 | spec: 25 | {{- with include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" ( list .Values.jobImage.pullSecrets ) "context" .) }} 26 | imagePullSecrets: 27 | {{- . | nindent 8 -}} 28 | {{- end }} 29 | containers: 30 | - name: patch 31 | image: {{ include "newrelic.common.images.image" ( dict "defaultRegistry" "registry.k8s.io" "imageRoot" .Values.jobImage "context" .) }} 32 | imagePullPolicy: {{ .Values.jobImage.pullPolicy }} 33 | {{- with include "newrelic.common.securityContext.container" . }} 34 | securityContext: 35 | {{- . | nindent 12 }} 36 | {{- end }} 37 | args: 38 | - patch 39 | - --webhook-name={{ include "newrelic.common.naming.fullname" . }} 40 | - --namespace={{ .Release.Namespace }} 41 | - --secret-name={{ include "nri-metadata-injection.fullname.admission" . }} 42 | - --patch-failure-policy=Ignore 43 | - --patch-validating=false 44 | {{- if .Values.jobImage.volumeMounts }} 45 | volumeMounts: 46 | {{- .Values.jobImage.volumeMounts | toYaml | nindent 10 }} 47 | {{- end }} 48 | {{- if .Values.jobImage.volumes }} 49 | volumes: 50 | {{- .Values.jobImage.volumes | toYaml | nindent 8 }} 51 | {{- end }} 52 | restartPolicy: OnFailure 53 | serviceAccountName: {{ include "nri-metadata-injection.fullname.admission.serviceAccount" . }} 54 | securityContext: 55 | runAsGroup: 2000 56 | runAsNonRoot: true 57 | runAsUser: 2000 58 | nodeSelector: 59 | kubernetes.io/os: linux 60 | {{ include "newrelic.common.nodeSelector" . | nindent 8 }} 61 | {{- if .Values.tolerations }} 62 | tolerations: 63 | {{- toYaml .Values.tolerations | nindent 8 }} 64 | {{- end }} 65 | {{- end }} 66 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/templates/admission-webhooks/job-patch/psp.yaml: -------------------------------------------------------------------------------- 1 | {{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled) (.Values.rbac.pspEnabled) (.Capabilities.APIVersions.Has "policy/v1beta1/PodSecurityPolicy")) }} 2 | apiVersion: policy/v1beta1 3 | kind: PodSecurityPolicy 4 | metadata: 5 | name: {{ include "nri-metadata-injection.fullname.admission" . }} 6 | annotations: 7 | "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade 8 | "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded 9 | labels: 10 | app: {{ include "nri-metadata-injection.name.admission" . }} 11 | {{- include "newrelic.common.labels" . | nindent 4 }} 12 | spec: 13 | privileged: false 14 | # Required to prevent escalations to root. 15 | # allowPrivilegeEscalation: false 16 | # This is redundant with non-root + disallow privilege escalation, 17 | # but we can provide it for defense in depth. 18 | #requiredDropCapabilities: 19 | # - ALL 20 | # Allow core volume types. 21 | volumes: 22 | - 'configMap' 23 | - 'emptyDir' 24 | - 'projected' 25 | - 'secret' 26 | - 'downwardAPI' 27 | - 'persistentVolumeClaim' 28 | hostNetwork: false 29 | hostIPC: false 30 | hostPID: false 31 | runAsUser: 32 | # Permits the container to run with root privileges as well. 33 | rule: 'RunAsAny' 34 | seLinux: 35 | # This policy assumes the nodes are using AppArmor rather than SELinux. 36 | rule: 'RunAsAny' 37 | supplementalGroups: 38 | rule: 'MustRunAs' 39 | ranges: 40 | # Forbid adding the root group. 41 | - min: 0 42 | max: 65535 43 | fsGroup: 44 | rule: 'MustRunAs' 45 | ranges: 46 | # Forbid adding the root group. 47 | - min: 0 48 | max: 65535 49 | readOnlyRootFilesystem: false 50 | {{- end }} 51 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/templates/admission-webhooks/job-patch/role.yaml: -------------------------------------------------------------------------------- 1 | {{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: {{ include "nri-metadata-injection.fullname.admission" . }} 6 | namespace: {{ .Release.Namespace }} 7 | annotations: 8 | "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade 9 | "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded 10 | labels: 11 | app: {{ include "nri-metadata-injection.name.admission" . }} 12 | {{- include "newrelic.common.labels" . | nindent 4 }} 13 | rules: 14 | - apiGroups: 15 | - "" 16 | resources: 17 | - secrets 18 | verbs: 19 | - get 20 | - create 21 | {{- end }} 22 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/templates/admission-webhooks/job-patch/rolebinding.yaml: -------------------------------------------------------------------------------- 1 | {{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: RoleBinding 4 | metadata: 5 | name: {{ include "nri-metadata-injection.fullname.admission" . }} 6 | namespace: {{ .Release.Namespace }} 7 | annotations: 8 | "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade 9 | "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded 10 | labels: 11 | app: {{ include "nri-metadata-injection.name.admission" . }} 12 | {{- include "newrelic.common.labels" . | nindent 4 }} 13 | roleRef: 14 | apiGroup: rbac.authorization.k8s.io 15 | kind: Role 16 | name: {{ include "nri-metadata-injection.fullname.admission" . }} 17 | subjects: 18 | - kind: ServiceAccount 19 | name: {{ include "nri-metadata-injection.fullname.admission.serviceAccount" . }} 20 | namespace: {{ .Release.Namespace }} 21 | {{- end }} 22 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/templates/admission-webhooks/job-patch/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- $createServiceAccount := include "newrelic.common.serviceAccount.create" . -}} 2 | {{- if (and $createServiceAccount (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) -}} 3 | apiVersion: v1 4 | kind: ServiceAccount 5 | metadata: 6 | name: {{ include "nri-metadata-injection.fullname.admission.serviceAccount" . }} 7 | namespace: {{ .Release.Namespace }} 8 | annotations: 9 | "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade 10 | "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded 11 | labels: 12 | app: {{ include "nri-metadata-injection.name.admission" . }} 13 | {{- include "newrelic.common.labels" . | nindent 4 }} 14 | {{- end }} 15 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/templates/admission-webhooks/mutatingWebhookConfiguration.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: admissionregistration.k8s.io/v1 2 | kind: MutatingWebhookConfiguration 3 | metadata: 4 | name: {{ include "newrelic.common.naming.fullname" . }} 5 | {{- if .Values.certManager.enabled }} 6 | annotations: 7 | certmanager.k8s.io/inject-ca-from: {{ printf "%s/%s-root-cert" .Release.Namespace (include "newrelic.common.naming.fullname" .) | quote }} 8 | cert-manager.io/inject-ca-from: {{ printf "%s/%s-root-cert" .Release.Namespace (include "newrelic.common.naming.fullname" .) | quote }} 9 | {{- end }} 10 | labels: 11 | {{- include "newrelic.common.labels" . | nindent 4 }} 12 | webhooks: 13 | - name: metadata-injection.newrelic.com 14 | clientConfig: 15 | service: 16 | name: {{ include "newrelic.common.naming.fullname" . }} 17 | namespace: {{ .Release.Namespace }} 18 | path: "/mutate" 19 | {{- if not .Values.certManager.enabled }} 20 | caBundle: "" 21 | {{- end }} 22 | rules: 23 | - operations: ["CREATE"] 24 | apiGroups: [""] 25 | apiVersions: ["v1"] 26 | resources: ["pods"] 27 | {{- if .Values.injectOnlyLabeledNamespaces }} 28 | scope: Namespaced 29 | namespaceSelector: 30 | matchLabels: 31 | newrelic-metadata-injection: enabled 32 | {{- end }} 33 | failurePolicy: Ignore 34 | timeoutSeconds: {{ .Values.timeoutSeconds }} 35 | sideEffects: None 36 | admissionReviewVersions: ["v1", "v1beta1"] 37 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/templates/cert-manager.yaml: -------------------------------------------------------------------------------- 1 | {{ if .Values.certManager.enabled }} 2 | --- 3 | # Create a selfsigned Issuer, in order to create a root CA certificate for 4 | # signing webhook serving certificates 5 | apiVersion: cert-manager.io/v1 6 | kind: Issuer 7 | metadata: 8 | name: {{ include "nri-metadata-injection.fullname.self-signed-issuer" . }} 9 | namespace: {{ .Release.Namespace }} 10 | spec: 11 | selfSigned: {} 12 | --- 13 | # Generate a CA Certificate used to sign certificates for the webhook 14 | apiVersion: cert-manager.io/v1 15 | kind: Certificate 16 | metadata: 17 | name: {{ include "newrelic.common.naming.fullname" . }}-root-cert 18 | namespace: {{ .Release.Namespace }} 19 | spec: 20 | secretName: {{ include "newrelic.common.naming.fullname" . }}-root-cert 21 | duration: {{ .Values.certManager.rootCertificateDuration}} 22 | issuerRef: 23 | name: {{ include "nri-metadata-injection.fullname.self-signed-issuer" . }} 24 | commonName: "ca.webhook.nri" 25 | isCA: true 26 | --- 27 | # Create an Issuer that uses the above generated CA certificate to issue certs 28 | apiVersion: cert-manager.io/v1 29 | kind: Issuer 30 | metadata: 31 | name: {{ include "nri-metadata-injection.fullname.root-issuer" . }} 32 | namespace: {{ .Release.Namespace }} 33 | spec: 34 | ca: 35 | secretName: {{ include "newrelic.common.naming.fullname" . }}-root-cert 36 | --- 37 | 38 | # Finally, generate a serving certificate for the webhook to use 39 | apiVersion: cert-manager.io/v1 40 | kind: Certificate 41 | metadata: 42 | name: {{ include "nri-metadata-injection.fullname.webhook-cert" . }} 43 | namespace: {{ .Release.Namespace }} 44 | spec: 45 | secretName: {{ include "nri-metadata-injection.fullname.admission" . }} 46 | duration: {{ .Values.certManager.webhookCertificateDuration }} 47 | issuerRef: 48 | name: {{ include "nri-metadata-injection.fullname.root-issuer" . }} 49 | dnsNames: 50 | - {{ include "newrelic.common.naming.fullname" . }} 51 | - {{ include "newrelic.common.naming.fullname" . }}.{{ .Release.Namespace }} 52 | - {{ include "newrelic.common.naming.fullname" . }}.{{ .Release.Namespace }}.svc 53 | {{ end }} 54 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "newrelic.common.naming.fullname" . }} 5 | namespace: {{ .Release.Namespace }} 6 | labels: 7 | {{- include "newrelic.common.labels" . | nindent 4 }} 8 | spec: 9 | replicas: {{ .Values.replicas }} 10 | selector: 11 | matchLabels: 12 | {{- /* We cannot use the common library here because of a legacy issue */}} 13 | {{- /* `selector` is immutable and the previous chart did not have all the idiomatic labels */}} 14 | app.kubernetes.io/name: {{ include "newrelic.common.naming.name" . }} 15 | template: 16 | metadata: 17 | {{- if .Values.podAnnotations }} 18 | annotations: 19 | {{- toYaml .Values.podAnnotations | nindent 8 }} 20 | {{- end }} 21 | labels: 22 | {{- include "newrelic.common.labels.podLabels" . | nindent 8 }} 23 | spec: 24 | {{- with include "nri-metadata-injection.securityContext.pod" . }} 25 | securityContext: 26 | {{- . | nindent 8 -}} 27 | {{- end }} 28 | {{- with include "newrelic.common.priorityClassName" . }} 29 | priorityClassName: {{ . }} 30 | {{- end }} 31 | {{- with include "newrelic.common.dnsConfig" . }} 32 | dnsConfig: 33 | {{- . | nindent 8 }} 34 | {{- end }} 35 | hostNetwork: {{ include "newrelic.common.hostNetwork.value" . }} 36 | {{- if include "newrelic.common.hostNetwork" . }} 37 | dnsPolicy: ClusterFirstWithHostNet 38 | {{- end }} 39 | 40 | {{- with include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" ( list .Values.image.pullSecrets ) "context" .) }} 41 | imagePullSecrets: 42 | {{- . | nindent 8 -}} 43 | {{- end }} 44 | containers: 45 | - name: {{ include "newrelic.common.naming.name" . }} 46 | image: {{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.image "context" .) }} 47 | imagePullPolicy: {{ .Values.image.pullPolicy }} 48 | {{- with include "newrelic.common.securityContext.container" . }} 49 | securityContext: 50 | {{- . | nindent 10 }} 51 | {{- end }} 52 | env: 53 | - name: clusterName 54 | value: {{ include "newrelic.common.cluster" . }} 55 | ports: 56 | - containerPort: 8443 57 | protocol: TCP 58 | volumeMounts: 59 | - name: tls-key-cert-pair 60 | mountPath: /etc/tls-key-cert-pair 61 | readinessProbe: 62 | httpGet: 63 | path: /health 64 | port: 8080 65 | initialDelaySeconds: 1 66 | periodSeconds: 1 67 | {{- if .Values.resources }} 68 | resources: 69 | {{ toYaml .Values.resources | nindent 10 }} 70 | {{- end }} 71 | volumes: 72 | - name: tls-key-cert-pair 73 | secret: 74 | secretName: {{ include "nri-metadata-injection.fullname.admission" . }} 75 | nodeSelector: 76 | kubernetes.io/os: linux 77 | {{ include "newrelic.common.nodeSelector" . | nindent 8 }} 78 | {{- with include "newrelic.common.tolerations" . }} 79 | tolerations: 80 | {{- . | nindent 8 -}} 81 | {{- end }} 82 | {{- with include "newrelic.common.affinity" . }} 83 | affinity: 84 | {{- . | nindent 8 -}} 85 | {{- end }} 86 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "newrelic.common.naming.fullname" . }} 5 | namespace: {{ .Release.Namespace }} 6 | labels: 7 | {{- include "newrelic.common.labels" . | nindent 4 }} 8 | spec: 9 | ports: 10 | - port: 443 11 | targetPort: 8443 12 | selector: 13 | {{- include "newrelic.common.labels.selectorLabels" . | nindent 4 }} 14 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/tests/cluster_test.yaml: -------------------------------------------------------------------------------- 1 | suite: test cluster environment variable setup 2 | templates: 3 | - templates/deployment.yaml 4 | release: 5 | name: release 6 | namespace: ns 7 | tests: 8 | - it: clusterName env is properly set 9 | set: 10 | cluster: my-cluster 11 | asserts: 12 | - contains: 13 | path: spec.template.spec.containers[0].env 14 | content: 15 | name: clusterName 16 | value: my-cluster 17 | - it: fail when cluster is not defined 18 | asserts: 19 | - failedTemplate: 20 | errorMessage: There is not cluster name definition set neither in `.global.cluster' nor `.cluster' in your values.yaml. Cluster name is required. 21 | - it: has a linux node selector by default 22 | set: 23 | cluster: my-cluster 24 | asserts: 25 | - equal: 26 | path: spec.template.spec.nodeSelector 27 | value: 28 | kubernetes.io/os: linux 29 | - it: has a linux node selector and additional selectors 30 | set: 31 | cluster: my-cluster 32 | nodeSelector: 33 | aCoolTestLabel: aCoolTestValue 34 | asserts: 35 | - equal: 36 | path: spec.template.spec.nodeSelector 37 | value: 38 | kubernetes.io/os: linux 39 | aCoolTestLabel: aCoolTestValue 40 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/tests/job_serviceaccount_test.yaml: -------------------------------------------------------------------------------- 1 | suite: test job' serviceAccount 2 | templates: 3 | - templates/admission-webhooks/job-patch/job-createSecret.yaml 4 | - templates/admission-webhooks/job-patch/job-patchWebhook.yaml 5 | release: 6 | name: my-release 7 | namespace: my-namespace 8 | tests: 9 | - it: RBAC points to the service account that is created by default 10 | set: 11 | cluster: test-cluster 12 | rbac.create: true 13 | serviceAccount.create: true 14 | asserts: 15 | - equal: 16 | path: spec.template.spec.serviceAccountName 17 | value: my-release-nri-metadata-injection-admission 18 | 19 | - it: RBAC points to the service account the user supplies when serviceAccount is disabled 20 | set: 21 | cluster: test-cluster 22 | rbac.create: true 23 | serviceAccount.create: false 24 | serviceAccount.name: sa-test 25 | asserts: 26 | - equal: 27 | path: spec.template.spec.serviceAccountName 28 | value: sa-test 29 | 30 | - it: RBAC points to the service account the user supplies when serviceAccount is disabled 31 | set: 32 | cluster: test-cluster 33 | rbac.create: true 34 | serviceAccount.create: false 35 | asserts: 36 | - equal: 37 | path: spec.template.spec.serviceAccountName 38 | value: default 39 | 40 | - it: has a linux node selector by default 41 | set: 42 | cluster: my-cluster 43 | asserts: 44 | - equal: 45 | path: spec.template.spec.nodeSelector 46 | value: 47 | kubernetes.io/os: linux 48 | 49 | - it: has a linux node selector and additional selectors 50 | set: 51 | cluster: my-cluster 52 | nodeSelector: 53 | aCoolTestLabel: aCoolTestValue 54 | asserts: 55 | - equal: 56 | path: spec.template.spec.nodeSelector 57 | value: 58 | kubernetes.io/os: linux 59 | aCoolTestLabel: aCoolTestValue 60 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/tests/rbac_test.yaml: -------------------------------------------------------------------------------- 1 | suite: test RBAC creation 2 | templates: 3 | - templates/admission-webhooks/job-patch/rolebinding.yaml 4 | - templates/admission-webhooks/job-patch/clusterrolebinding.yaml 5 | release: 6 | name: my-release 7 | namespace: my-namespace 8 | tests: 9 | - it: RBAC points to the service account that is created by default 10 | set: 11 | cluster: test-cluster 12 | rbac.create: true 13 | serviceAccount.create: true 14 | asserts: 15 | - equal: 16 | path: subjects[0].name 17 | value: my-release-nri-metadata-injection-admission 18 | 19 | - it: RBAC points to the service account the user supplies when serviceAccount is disabled 20 | set: 21 | cluster: test-cluster 22 | rbac.create: true 23 | serviceAccount.create: false 24 | serviceAccount.name: sa-test 25 | asserts: 26 | - equal: 27 | path: subjects[0].name 28 | value: sa-test 29 | 30 | - it: RBAC points to the service account the user supplies when serviceAccount is disabled 31 | set: 32 | cluster: test-cluster 33 | rbac.create: true 34 | serviceAccount.create: false 35 | asserts: 36 | - equal: 37 | path: subjects[0].name 38 | value: default 39 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/tests/volume_mounts_test.yaml: -------------------------------------------------------------------------------- 1 | suite: check volume mounts is properly set 2 | templates: 3 | - templates/admission-webhooks/job-patch/job-createSecret.yaml 4 | - templates/admission-webhooks/job-patch/job-patchWebhook.yaml 5 | release: 6 | name: release 7 | namespace: ns 8 | tests: 9 | - it: clusterName env is properly set 10 | set: 11 | cluster: my-cluster 12 | jobImage: 13 | volumeMounts: 14 | - name: test-volume 15 | volumePath: /test-volume 16 | volumes: 17 | - name: test-volume-container 18 | emptyDir: {} 19 | 20 | asserts: 21 | - contains: 22 | path: spec.template.spec.containers[0].volumeMounts 23 | content: 24 | name: test-volume 25 | volumePath: /test-volume 26 | - contains: 27 | path: spec.template.spec.volumes 28 | content: 29 | name: test-volume-container 30 | emptyDir: {} 31 | -------------------------------------------------------------------------------- /charts/nri-metadata-injection/values.yaml: -------------------------------------------------------------------------------- 1 | # -- Override the name of the chart 2 | nameOverride: "" 3 | # -- Override the full name of the release 4 | fullnameOverride: "" 5 | 6 | # -- Name of the Kubernetes cluster monitored. Can be configured also with `global.cluster` 7 | cluster: "" 8 | 9 | # -- Image for the New Relic Metadata Injector 10 | # @default -- See `values.yaml` 11 | image: 12 | registry: 13 | repository: newrelic/k8s-metadata-injection 14 | tag: "" # Defaults to chart's appVersion 15 | pullPolicy: IfNotPresent 16 | # -- The secrets that are needed to pull images from a custom registry. 17 | pullSecrets: [] 18 | # - name: regsecret 19 | 20 | # -- Image for creating the needed certificates of this webhook to work 21 | # @default -- See `values.yaml` 22 | jobImage: 23 | registry: # Defaults to registry.k8s.io 24 | repository: ingress-nginx/kube-webhook-certgen 25 | tag: v1.4.3 26 | pullPolicy: IfNotPresent 27 | # -- The secrets that are needed to pull images from a custom registry. 28 | pullSecrets: [] 29 | # - name: regsecret 30 | 31 | # -- Volume mounts to add to the job, you might want to mount tmp if Pod Security Policies 32 | # Enforce a read-only root. 33 | volumeMounts: [] 34 | # - name: tmp 35 | # mountPath: /tmp 36 | 37 | # -- Volumes to add to the job container 38 | volumes: [] 39 | # - name: tmp 40 | # emptyDir: {} 41 | 42 | rbac: 43 | # rbac.pspEnabled -- Whether the chart should create Pod Security Policy objects. 44 | pspEnabled: false 45 | 46 | replicas: 1 47 | 48 | # -- Additional labels for chart objects. Can be configured also with `global.labels` 49 | labels: {} 50 | # -- Annotations to be added to all pods created by the integration. 51 | podAnnotations: {} 52 | # -- Additional labels for chart pods. Can be configured also with `global.podLabels` 53 | podLabels: {} 54 | 55 | # -- Image for creating the needed certificates of this webhook to work 56 | # @default -- 100m/30M -/80M 57 | resources: 58 | limits: 59 | memory: 80M 60 | requests: 61 | cpu: 100m 62 | memory: 30M 63 | 64 | # -- Sets pod's priorityClassName. Can be configured also with `global.priorityClassName` 65 | priorityClassName: "" 66 | # -- (bool) Sets pod's hostNetwork. Can be configured also with `global.hostNetwork` 67 | # @default -- false 68 | hostNetwork: 69 | # -- Sets pod's dnsConfig. Can be configured also with `global.dnsConfig` 70 | dnsConfig: {} 71 | # -- Sets security context (at pod level). Can be configured also with `global.podSecurityContext` 72 | podSecurityContext: {} 73 | # -- Sets security context (at container level). Can be configured also with `global.containerSecurityContext` 74 | containerSecurityContext: {} 75 | 76 | certManager: 77 | # certManager.enabled -- Use cert manager for webhook certs 78 | enabled: false 79 | # -- Sets the root certificate duration. Defaults to 43800h (5 years). 80 | rootCertificateDuration: 43800h 81 | # -- Sets certificate duration. Defaults to 8760h (1 year). 82 | webhookCertificateDuration: 8760h 83 | 84 | # -- Sets pod/node affinities. Can be configured also with `global.affinity` 85 | affinity: {} 86 | # -- Sets pod's node selector. Can be configured also with `global.nodeSelector` 87 | nodeSelector: {} 88 | # -- Sets pod's tolerations to node taints. Can be configured also with `global.tolerations` 89 | tolerations: [] 90 | 91 | # -- Enable the metadata decoration only for pods living in namespaces labeled 92 | # with 'newrelic-metadata-injection=enabled'. 93 | injectOnlyLabeledNamespaces: false 94 | 95 | # -- Use custom tls certificates for the webhook, or let the chart handle it 96 | # automatically. 97 | # Ref: https://docs.newrelic.com/docs/integrations/kubernetes-integration/link-your-applications/link-your-applications-kubernetes#configure-injection 98 | customTLSCertificate: false 99 | 100 | # -- Webhook timeout 101 | # Ref: https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#timeouts 102 | timeoutSeconds: 28 103 | -------------------------------------------------------------------------------- /cmd/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "os" 10 | "os/signal" 11 | "path/filepath" 12 | "strings" 13 | "syscall" 14 | "time" 15 | 16 | "github.com/fsnotify/fsnotify" 17 | "github.com/kelseyhightower/envconfig" 18 | "go.uber.org/zap" 19 | "go.uber.org/zap/zapcore" 20 | 21 | "github.com/newrelic/k8s-metadata-injection/src/server" 22 | ) 23 | 24 | const ( 25 | appName = "new-relic-k8s-metadata-injection" 26 | ) 27 | 28 | // specification contains the specs for this app. 29 | type specification struct { 30 | Port int `default:"8443"` // Webhook server port. 31 | TLSCertFile string `default:"/etc/tls-key-cert-pair/tls.crt" envconfig:"tls_cert_file"` // File containing the x509 Certificate for HTTPS. 32 | TLSKeyFile string `default:"/etc/tls-key-cert-pair/tls.key" envconfig:"tls_key_file"` // File containing the x509 private key for TLSCERTFILE. 33 | ClusterName string `default:"cluster" split_words:"true"` // The name of the Kubernetes cluster. 34 | Timeout time.Duration `default:"1s"` // Server timeout for the pod mutation. 35 | } 36 | 37 | func main() { 38 | var s specification 39 | err := envconfig.Process(strings.Replace(appName, "-", "_", -1), &s) 40 | if err != nil { 41 | log.Fatal(err.Error()) 42 | } 43 | 44 | logger := setupLogger() 45 | defer func() { _ = logger.Sync() }() 46 | 47 | pair, err := tls.LoadX509KeyPair(s.TLSCertFile, s.TLSKeyFile) 48 | if err != nil { 49 | logger.Errorw("failed to load key pair", "err", err) 50 | } 51 | 52 | watcher, _ := fsnotify.NewWatcher() 53 | defer func() { _ = watcher.Close() }() 54 | // Watch the parent directory of the key/cert files so we can catch 55 | // symlink updates of k8s secrets volumes and reload the certificates whenever they change. 56 | watchDir, _ := filepath.Split(s.TLSCertFile) 57 | if err := watcher.Add(watchDir); err != nil { 58 | logger.Errorw("could not watch folder", "folder", watchDir, "err", err) 59 | } 60 | 61 | whsvr := &server.Webhook{ 62 | KeyFile: s.TLSKeyFile, 63 | CertFile: s.TLSCertFile, 64 | Cert: &pair, 65 | ClusterName: s.ClusterName, 66 | CertWatcher: watcher, 67 | Server: &http.Server{ 68 | Addr: fmt.Sprintf(":%d", s.Port), 69 | }, 70 | Logger: logger, 71 | } 72 | whsvr.Server.TLSConfig = &tls.Config{GetCertificate: whsvr.GetCert} 73 | 74 | mux := http.NewServeMux() 75 | mux.Handle("/mutate", withLoggingMiddleware(logger)(withTimeoutMiddleware(s.Timeout)(whsvr))) 76 | whsvr.Server.Handler = mux 77 | 78 | // The health check needs to be in another server because it cannot be under TLS. 79 | readinessProbe := server.TLSReadyReadinessProbe(whsvr) 80 | go func() { 81 | logger.Info("starting the TLS readiness server") 82 | if err := http.ListenAndServe(":8080", readinessProbe); err != nil { 83 | logger.Errorw("failed to start TLS readiness server", "err", err) 84 | } 85 | }() 86 | 87 | go func() { 88 | logger.Info("starting the webhook server") 89 | if err := whsvr.Server.ListenAndServeTLS("", ""); err != nil { 90 | logger.Errorw("failed to start webhook server", "err", err) 91 | } 92 | }() 93 | 94 | signalChan := make(chan os.Signal, 1) 95 | signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) 96 | 97 | var debounceTimer <-chan time.Time 98 | for { 99 | select { 100 | case <-debounceTimer: 101 | pair, err := tls.LoadX509KeyPair(whsvr.CertFile, whsvr.KeyFile) 102 | if err != nil { 103 | logger.Errorw("reload cert error", "err", err) 104 | break 105 | } 106 | whsvr.Lock() 107 | whsvr.Cert = &pair 108 | whsvr.Unlock() 109 | logger.Info("cert/key pair reloaded!") 110 | case event := <-whsvr.CertWatcher.Events: 111 | if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create { 112 | debounceTimer = time.After(500 * time.Millisecond) 113 | } 114 | case <-signalChan: 115 | logger.Info("got OS shutdown signal, shutting down webhook server gracefully...") 116 | _ = watcher.Close() 117 | _ = whsvr.Server.Shutdown(context.Background()) 118 | return 119 | } 120 | } 121 | } 122 | 123 | func withTimeoutMiddleware(timeout time.Duration) func(next http.Handler) http.Handler { 124 | return func(next http.Handler) http.Handler { 125 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 126 | http.TimeoutHandler(next, timeout, "server timeout").ServeHTTP(w, r) 127 | }) 128 | } 129 | } 130 | 131 | func withLoggingMiddleware(logger *zap.SugaredLogger) func(next http.Handler) http.Handler { 132 | return func(next http.Handler) http.Handler { 133 | fn := func(w http.ResponseWriter, r *http.Request) { 134 | scheme := "http" 135 | if r.TLS != nil { 136 | scheme = "https" 137 | } 138 | logger.Infof("%s %s://%s%s %s\" from %s", r.Method, scheme, r.Host, r.RequestURI, r.Proto, r.RemoteAddr) 139 | 140 | next.ServeHTTP(w, r) 141 | } 142 | 143 | return http.HandlerFunc(fn) 144 | } 145 | } 146 | 147 | func setupLogger() *zap.SugaredLogger { 148 | config := zap.NewProductionConfig() 149 | config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // We want human readable timestamps. 150 | 151 | zapLogger, err := config.Build() 152 | if err != nil { 153 | log.Fatalf("can't initialize zap logger: %v", err) 154 | } 155 | return zapLogger.Sugar() 156 | } 157 | -------------------------------------------------------------------------------- /docs/k8s-api-lifecycle.svg: -------------------------------------------------------------------------------- 1 | Created with Raphaël 2.2.0K8s APIAuthentication / AuthorizationMutatingAdmissionWebhooks200 OKBody contains 'response' fieldBody contains 'allowed:true'Schema validationValidatingAdmissionWebhooksCreate objectEnd'FailurePolicy: Ignore'yesnoyesnoyesnoyesno -------------------------------------------------------------------------------- /docs/lifecycle.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Request internal lifecycle 2 | 3 | Kubernetes API serves as the foundation for the declarative configuration schema for the system, among others. Kubernetes itself is decomposed into multiple components, which interact through its API. 4 | 5 | Any request made to K8s API is affected by a timeout; by default **60 seconds** (see [source code](https://github.com/kubernetes/apiserver/blob/b8915a5609e4d7553d92f0d431ba04ecf9b52777/pkg/server/config.go#L262)). 6 | 7 | ## Mutating Webhook 8 | 9 | The injection of New Relic APM Metadata is implemented as an Admission Webhook (`MutatingAdmissionWebhook`) using [Kubernetes Admission Controllers](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers). 10 | 11 | Admission Controllers run as part of the Kubernetes API Request Lifecycle. 12 | 13 | > An admission controller is a piece of code that intercepts requests to the Kubernetes API server prior to persistence of the object, but after the request is authenticated and authorized. 14 | > 15 | > --- https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#what-are-they 16 | 17 | The default request timeout between the Kubernetes API and any Admission Webhook is **30 seconds** (the value is being forced. See [source code](https://github.com/kubernetes/apiserver/blob/e3d77264915da75023b171c7e370415e740851c7/pkg/util/webhook/webhook.go#L36)). 18 | 19 | ### Response 20 | 21 | Kubernetes API expects that successful calls return a `200 OK` HTTP code including a `ReviewResponse` in the body. 22 | 23 | #### Response schema 24 | 25 | Please refer to our [Open Api 3.0 spec](/openapi.yaml) file in order to know more about the shape of the response body. 26 | 27 | #### K8s API Error Handling 28 | 29 | Please find here a flow diagram of the internal Kubernetes API Lifecycle. Pay special attention to the [failurePolicy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.13/#webhook-v1beta1-admissionregistration) config value and how it determines the final behaviour. 30 | 31 | ![](k8s-api-lifecycle.svg) 32 | 33 | As an alternative, you can see the [source code](https://github.com/kubernetes/apiserver/blob/master/pkg/admission/plugin/webhook/mutating/dispatcher.go#L56) of the Dispatcher that executes the Mutating Webhook. 34 | 35 | #### Non-intrusive commitment 36 | 37 | We are committed to keep the webhook as a non-intrusive mechanism for injecting the required env vars into the containers. 38 | In order to achieve this we took the following design decisions: 39 | 40 | 1. The `failurePolicy` is set to `Ignore`. Any response from our webhook with a non success HTTP Status Code will be then skipped, so the Kubernetes API will continue the execution of the request lifecycle. 41 | 2. Any `200 OK` response coming from the webhook contains `allowed: true` within the `response` included in the body. This tells the Kubernetes API that the creation of such workload is allowed, letting then to continue the execution of the request lifecycle. 42 | -------------------------------------------------------------------------------- /docs/performance.md: -------------------------------------------------------------------------------- 1 | # Performance 2 | 3 | The injection of New Relic APM Metadata is implemented as a webhook using Kubernetes MutatingAdmissionWebhook, which is in fact an AdmissionWebhook. 4 | 5 | Admission webhooks run as part of the Kubernetes API Request Lifecycle. 6 | 7 | > An admission controller is a piece of code that intercepts requests to the Kubernetes API server prior to persistence of the object, but after the request is authenticated and authorized. 8 | > 9 | > --- https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#what-are-they 10 | 11 | One of our top priorities is to code it as lightweight as possible, making the webhook behave as non-intrusive and to perform almost at no cost. 12 | 13 | Therefore, we ran the following benchmark and performance tests. 14 | 15 | ## Kubernetes Request lifecycle 16 | 17 | Please refer to the [Request internal lifecycle](lifecycle.md) documentation. 18 | 19 | ## Benchmark of the webhook code 20 | 21 | The MutatingAdmissionWebhook code has been tested using Golang Benchmarks. 22 | 23 | The slowest run was on average 1005508 ns/op. The fastest on average was 434999 ns/op. The "average of the average" was 653154 ns/op. All these values are 1 millisecond or less. On a real word situation it will be slower due to different size of each pod's creation payload and to the TLS overhead. 24 | 25 | The code of the benchmark can be found [here](./webhook_test.go). 26 | 27 | ### Results 28 | 29 | * These tests were ran on a 2018 Macbook Pro with a core i7 2.7 GHz and 16 GB of memory. 30 | * They were recorded on Jan 4th, 2019. 31 | * Latest commit SHA was `ab4b0de131c4e2ea51089e1441d5e571baa3803e` 32 | 33 | 34 | ``` 35 | $ go test -bench . 36 | 37 | goos: darwin 38 | goarch: amd64 39 | pkg: github.com/newrelic/k8s-metadata-injection 40 | Benchmark_WebhookPerformance-8 3000 434999 ns/op 41 | PASS 42 | ok github.com/newrelic/k8s-metadata-injection 1.420s 43 | 44 | $ go test -bench . 45 | goos: darwin 46 | goarch: amd64 47 | pkg: github.com/newrelic/k8s-metadata-injection 48 | Benchmark_WebhookPerformance-8 2000 604123 ns/op 49 | PASS 50 | ok github.com/newrelic/k8s-metadata-injection 1.342s 51 | 52 | $ go test -bench . 53 | goos: darwin 54 | goarch: amd64 55 | pkg: github.com/newrelic/k8s-metadata-injection 56 | Benchmark_WebhookPerformance-8 3000 505401 ns/op 57 | PASS 58 | ok github.com/newrelic/k8s-metadata-injection 1.647s 59 | 60 | $ go test -bench . 61 | goos: darwin 62 | goarch: amd64 63 | pkg: github.com/newrelic/k8s-metadata-injection 64 | Benchmark_WebhookPerformance-8 2000 642582 ns/op 65 | PASS 66 | ok github.com/newrelic/k8s-metadata-injection 1.433s 67 | 68 | $ go test -bench . 69 | goos: darwin 70 | goarch: amd64 71 | pkg: github.com/newrelic/k8s-metadata-injection 72 | Benchmark_WebhookPerformance-8 2000 747675 ns/op 73 | PASS 74 | ok github.com/newrelic/k8s-metadata-injection 1.662s 75 | 76 | $ go test -bench . 77 | goos: darwin 78 | goarch: amd64 79 | pkg: github.com/newrelic/k8s-metadata-injection 80 | Benchmark_WebhookPerformance-8 2000 837064 ns/op 81 | PASS 82 | ok github.com/newrelic/k8s-metadata-injection 1.847s 83 | 84 | $ go test -bench . 85 | goos: darwin 86 | goarch: amd64 87 | pkg: github.com/newrelic/k8s-metadata-injection 88 | Benchmark_WebhookPerformance-8 1000 1005508 ns/op 89 | PASS 90 | ok github.com/newrelic/k8s-metadata-injection 1.183s 91 | 92 | $ go test -bench . 93 | goos: darwin 94 | goarch: amd64 95 | pkg: github.com/newrelic/k8s-metadata-injection 96 | Benchmark_WebhookPerformance-8 3000 447884 ns/op 97 | PASS 98 | ok github.com/newrelic/k8s-metadata-injection 1.461s 99 | ``` 100 | 101 | ## Benchmark having the Mutating Webhook in place 102 | 103 | After running the code benchmark we realized that running benchmarks in real clusters are not important. As the service is simple and extremely fast, it should have a close to **zero** perceptive impact in pod creation in the cases where it would run. 104 | 105 | We will use different tools to ensure performance is focused in this service in the form of Golang benchmarks and a server side timeout to prevent perceptive interference in pod creation time. 106 | 107 | -------------------------------------------------------------------------------- /e2e-tests/k8s-e2e-bootstraping.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | E2E_KUBERNETES_VERSION=${E2E_KUBERNETES_VERSION:-v1.32.0} 4 | E2E_MINIKUBE_DRIVER=${E2E_MINIKUBE_DRIVER:-docker} 5 | E2E_SUDO=${E2E_SUDO:-} 6 | 7 | start_minikube() { 8 | export MINIKUBE_WANTREPORTERRORPROMPT=false 9 | export MINIKUBE_HOME=$HOME 10 | export CHANGE_MINIKUBE_NONE_USER=true 11 | mkdir -p "$HOME"/.kube 12 | touch "$HOME"/.kube/config 13 | export KUBECONFIG=$HOME/.kube/config 14 | 15 | printf "Starting Minikube with Kubernetes version %s...\n" "${E2E_KUBERNETES_VERSION}" 16 | $E2E_SUDO minikube start --driver="$E2E_MINIKUBE_DRIVER" --kubernetes-version="$E2E_KUBERNETES_VERSION" 17 | } 18 | 19 | get_pod_name_by_label() { 20 | pod_name="" 21 | i=1 22 | while [ "$i" -ne 10 ] 23 | do 24 | pod_name=$(kubectl -n default get pods -l "$1" -o name | sed 's/pod\///g; s/pods\///g') 25 | if [ "$pod_name" != "" ]; then 26 | break 27 | fi 28 | sleep 1 29 | i=$((i + 1)) 30 | done 31 | printf "%s" "$pod_name" 32 | } 33 | 34 | wait_for_pod() { 35 | set +e 36 | desired_status=${2:-'Running'} 37 | is_pod_in_desired_status=false 38 | i=1 39 | while [ "$i" -ne 30 ] 40 | do 41 | pod_status="$(kubectl -n default get pod "$1" -o jsonpath='{.status.phase}')" 42 | if [ "$pod_status" = "$desired_status" ]; then 43 | is_pod_in_desired_status=true 44 | printf "pod %s is %s\n" "$1" "$desired_status" 45 | break 46 | fi 47 | 48 | printf "Waiting for pod %s to be %s\n" "$1" "$desired_status" 49 | sleep 3 50 | i=$((i + 1)) 51 | done 52 | if [ $is_pod_in_desired_status = "false" ]; then 53 | printf "pod %s does not transition to %s within 1 minute 30 seconds\n" "$1" "$desired_status" 54 | kubectl -n default get pods 55 | kubectl -n default describe pod "$1" 56 | exit 1 57 | fi 58 | set -e 59 | } 60 | 61 | ### Bootstraping 62 | 63 | cd "$(dirname "$0")" 64 | 65 | start_minikube 66 | minikube version 67 | minikube update-context 68 | 69 | is_kube_running="false" 70 | 71 | set +e 72 | # this for loop waits until kubectl can access the api server that Minikube has created 73 | i=1 74 | while [ "$i" -ne 90 ] # timeout for 3 minutes 75 | do 76 | kubectl get po 1>/dev/null 2>&1 77 | if [ $? -ne 1 ]; then 78 | is_kube_running="true" 79 | break 80 | fi 81 | 82 | printf "waiting for Kubernetes cluster up\n" 83 | sleep 2 84 | i=$((i + 1)) 85 | done 86 | 87 | if [ $is_kube_running = "false" ]; then 88 | minikube logs 89 | printf "Kubernetes did not start within 3 minutes. Something went wrong.\n" 90 | exit 1 91 | fi 92 | set -e 93 | -------------------------------------------------------------------------------- /e2e-tests/tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | printf 'bootstrapping starts:\n' 5 | # shellcheck disable=SC1090 6 | . "$(dirname "$0")/k8s-e2e-bootstraping.sh" 7 | printf 'bootstrapping complete\n' 8 | 9 | HELM_RELEASE_NAME="nri-metadata-injection" 10 | WEBHOOK_LABEL="app.kubernetes.io/name=nri-metadata-injection,app.kubernetes.io/instance=${HELM_RELEASE_NAME}" 11 | DUMMY_DEPLOYMENT_NAME="dummy-deployment" 12 | DUMMY_POD_LABEL="app=${DUMMY_DEPLOYMENT_NAME}" 13 | ENV_VARS_PREFIX="NEW_RELIC_METADATA_KUBERNETES" 14 | NAMESPACE_NAME="$(kubectl config view --minify --output 'jsonpath={..namespace}')" 15 | IMAGE_NAME="e2e/k8s-metadata-injection" 16 | IMAGE_TAG="e2e" 17 | 18 | finish() { 19 | printf "webhook logs:\n" 20 | kubectl logs "$(get_pod_name_by_label "$WEBHOOK_LABEL")" || true 21 | 22 | helm uninstall "$HELM_RELEASE_NAME" || true 23 | kubectl delete deployment ${DUMMY_DEPLOYMENT_NAME} || true 24 | } 25 | 26 | # ensure that we build docker image in minikube 27 | [ "$E2E_MINIKUBE_DRIVER" = "none" ] || eval "$(minikube docker-env --shell bash)" 28 | 29 | # build webhook docker image 30 | 31 | # Set GOOS and GOARCH explicitly since Dockerfile expects them in the binary name 32 | GOOS="linux" GOARCH="amd64" IMAGE_NAME="$IMAGE_NAME" DOCKER_IMAGE_TAG="$IMAGE_TAG" make -C .. compile build-container 33 | 34 | trap finish EXIT 35 | chmod go-r /home/runner/.kube/config 36 | # install the metadata-injection webhook 37 | helm repo add newrelic https://helm-charts.newrelic.com 38 | helm dependency build ../charts/nri-metadata-injection 39 | if ! helm upgrade --install "$HELM_RELEASE_NAME" ../charts/nri-metadata-injection \ 40 | --wait \ 41 | --set cluster=YOUR-CLUSTER-NAME \ 42 | --set image.pullPolicy=Never \ 43 | --set image.tag="$IMAGE_TAG" 44 | then 45 | printf "Helm failed to install this release\n" 46 | exit 1 47 | fi 48 | 49 | ### Testing 50 | 51 | # deploy a pod 52 | kubectl create deployment "$DUMMY_DEPLOYMENT_NAME" --image=nginx:latest --dry-run=client -o yaml | kubectl apply -f- 53 | 54 | pod_name="$(get_pod_name_by_label "$DUMMY_POD_LABEL")" 55 | if [ "$pod_name" = "" ]; then 56 | printf "not found any pod with label %s\n" "$DUMMY_POD_LABEL" 57 | kubectl describe deployment "$DUMMY_DEPLOYMENT_NAME" 58 | exit 1 59 | fi 60 | wait_for_pod "$pod_name" 61 | 62 | kubectl get pods 63 | kubectl describe pod "${pod_name}" 64 | 65 | printf "getting env vars for %s\n" "${pod_name}" 66 | set +e # This grep can be empty in the webhook is not correctly running and we want logs and a proper error 67 | date 68 | env_vars="$(kubectl exec "${pod_name}" -- env | grep "${ENV_VARS_PREFIX}")" 69 | set -e 70 | printf "\nInjected environment variables:\n" 71 | printf "%s\n" "$env_vars" 72 | 73 | errors="" 74 | for PAIR in \ 75 | "CLUSTER_NAME YOUR-CLUSTER-NAME" \ 76 | "NODE_NAME minikube" \ 77 | "NAMESPACE_NAME ${NAMESPACE_NAME}" \ 78 | "POD_NAME ${pod_name}" \ 79 | "CONTAINER_NAME nginx" \ 80 | "CONTAINER_IMAGE_NAME nginx:latest" \ 81 | "DEPLOYMENT_NAME ${DUMMY_DEPLOYMENT_NAME}" 82 | do 83 | k=$(echo "$PAIR" | awk '{ print $1 }') 84 | v=$(echo "$PAIR" | awk '{ print $2 }') 85 | if ! echo "$env_vars" | grep -q "${ENV_VARS_PREFIX}_${k}=${v}$"; then 86 | errors="${errors}\n${ENV_VARS_PREFIX}_${k}=${v} is not present" 87 | fi 88 | done 89 | 90 | if [ -n "$errors" ]; then 91 | printf "Test errors:%s\n" "$errors" 92 | exit 1 93 | else 94 | printf "Tests are passing successfully\n\n" 95 | fi 96 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | export NEW_RELIC_K8S_METADATA_INJECTION_CLUSTER_NAME=${clusterName} 4 | 5 | exec /app/k8s-metadata-injection 2>&1 6 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/newrelic/k8s-metadata-injection 2 | 3 | go 1.24.3 4 | 5 | require ( 6 | github.com/fsnotify/fsnotify v1.9.0 7 | github.com/kelseyhightower/envconfig v1.4.0 8 | github.com/stretchr/testify v1.10.0 9 | go.uber.org/zap v1.27.0 10 | k8s.io/api v0.33.0 11 | k8s.io/apimachinery v0.33.0 12 | ) 13 | 14 | require ( 15 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 16 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 17 | github.com/go-logr/logr v1.4.2 // indirect 18 | github.com/gogo/protobuf v1.3.2 // indirect 19 | github.com/google/gofuzz v1.2.0 // indirect 20 | github.com/json-iterator/go v1.1.12 // indirect 21 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 22 | github.com/modern-go/reflect2 v1.0.2 // indirect 23 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 24 | github.com/x448/float16 v0.8.4 // indirect 25 | go.uber.org/multierr v1.10.0 // indirect 26 | golang.org/x/net v0.38.0 // indirect 27 | golang.org/x/sys v0.33.0 // indirect 28 | golang.org/x/text v0.25.0 // indirect 29 | gopkg.in/inf.v0 v0.9.1 // indirect 30 | gopkg.in/yaml.v3 v3.0.1 // indirect 31 | k8s.io/klog/v2 v2.130.1 // indirect 32 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect 33 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 34 | sigs.k8s.io/randfill v1.0.0 // indirect 35 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 36 | sigs.k8s.io/yaml v1.4.0 // indirect 37 | ) 38 | 39 | // To avoid CVE-2022-27191 triggering a security scan 40 | replace golang.org/x/crypto => golang.org/x/crypto v0.38.0 41 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 4 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= 6 | github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 7 | github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= 8 | github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 9 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 10 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 11 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 12 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 13 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 14 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 15 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 16 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 17 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 18 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 19 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 20 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 21 | github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= 22 | github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= 23 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 24 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 25 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 26 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 27 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 28 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 29 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 30 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 31 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 32 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 33 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 34 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 35 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 36 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 37 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 38 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 39 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 40 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 41 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 42 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 43 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 44 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 45 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 46 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 47 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 48 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 49 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 50 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 51 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 52 | go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= 53 | go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 54 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 55 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 56 | golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= 57 | golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= 58 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 59 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 60 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 61 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 62 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 63 | golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 64 | golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 65 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 66 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 67 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 68 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 69 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 70 | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 71 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 72 | golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 73 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 74 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 75 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 76 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 77 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 78 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 79 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 80 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 81 | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 82 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 83 | golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 84 | golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 85 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 86 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 87 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 88 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 89 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 90 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 91 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 92 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 93 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 94 | golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 95 | golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 96 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 97 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 98 | golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= 99 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 100 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 101 | golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= 102 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 103 | golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= 104 | golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= 105 | golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= 106 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 107 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 108 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 109 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 110 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 111 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 112 | golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 113 | golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= 114 | golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 115 | golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= 116 | golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= 117 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 118 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 119 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 120 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 121 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 122 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 123 | golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 124 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 125 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 126 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 127 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 128 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 129 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 130 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 131 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 132 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 133 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 134 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 135 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 136 | k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= 137 | k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= 138 | k8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU= 139 | k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM= 140 | k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= 141 | k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= 142 | k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ= 143 | k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= 144 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 145 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 146 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= 147 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 148 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= 149 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= 150 | sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= 151 | sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= 152 | sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= 153 | sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= 154 | sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= 155 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= 156 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= 157 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 158 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 159 | -------------------------------------------------------------------------------- /openapi.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | description: >- 4 | The injection of New Relic APM Metadata is implemented as a webhook using 5 | Kubernetes MutatingAdmissionWebhook. 6 | title: New Relic Kubernetes Metadata Injection MutatingAdmissionWebhook 7 | version: 1.0.0 8 | servers: 9 | - url: 'https://newrelic-metadata-injection-svc.default.svc/' 10 | paths: 11 | /mutate: 12 | post: 13 | parameters: 14 | - description: >- 15 | The expected timeout. This value is the timeout that the Kubernetes 16 | API client uses when calling the webhook. This is provided in order 17 | to cancel the operation in case it reaches such timeout since 18 | Kubernetes will discard it anyway. 19 | example: >- 20 | 30s 21 | in: query 22 | name: timeout 23 | schema: 24 | type: string 25 | responses: 26 | '200': 27 | content: 28 | application/json: 29 | schema: 30 | $ref: '#/components/schemas/AdmissionReviewResponse' 31 | description: >- 32 | The request was successful. Note that in case the status field is 33 | present, the mutation was not executed. 34 | '400': 35 | description: Bad request. 36 | '500': 37 | description: Internal server error. 38 | description: >- 39 | The injection of New Relic APM Metadata is implemented as a webhook 40 | using Kubernetes MutatingAdmissionWebhook. This is the entry point of 41 | such Webhook. 42 | operationId: mutate 43 | requestBody: 44 | content: 45 | application/json: 46 | schema: 47 | $ref: '#/components/schemas/AdmissionReviewRequest' 48 | description: >- 49 | `AdmissionReview` with Request object. It contains the info about the 50 | object to be mutated. 51 | required: true 52 | summary: Mutate pod. Inject New Relic Agent env vars. 53 | components: 54 | schemas: 55 | AdmissionRequest: 56 | properties: 57 | uid: 58 | description: >- 59 | Identifier for the individual request/response. This should be 60 | copied over from the corresponding AdmissionRequest 61 | type: string 62 | type: object 63 | AdmissionResponse: 64 | properties: 65 | allowed: 66 | default: false 67 | description: >- 68 | Indicates whether or not the admission request was permitted. If 69 | false, the creation of the related workload will fail no matters the 70 | chosen failurePolicy 71 | type: boolean 72 | patch: 73 | description: >- 74 | The patch body. Currently there is only support "JSONPatch" which 75 | implements RFC 6902 76 | example: >- 77 | W3sib3AiOiJhZGQiLCJwYXRoIjoiL3NwZWMvY29udGFpbmVycy8wL2VudiIsInZhbHVlIjpbeyJuYW1lIjoiTkVXX1JFTElDX01FVEFEQVRBX0tVQkVSTkVURVNfQ0xVU1RFUl9OQU1FIiwidmFsdWUiOiJmb29iYXIifV19LHsib3AiOiJhZGQiLCJwYXRoIjoiL3NwZWMvY29udGFpbmVycy8wL2Vudi8tIiwidmFsdWUiOnsibmFtZSI6Ik5FV19SRUxJQ19NRVRBREFUQV9LVUJFUk5FVEVTX05PREVfTkFNRSIsInZhbHVlRnJvbSI6eyJmaWVsZFJlZiI6eyJmaWVsZFBhdGgiOiJzcGVjLm5vZGVOYW1lIn19fX0seyJvcCI6ImFkZCIsInBhdGgiOiIvc3BlYy9jb250YWluZXJzLzAvZW52Ly0iLCJ2YWx1ZSI6eyJuYW1lIjoiTkVXX1JFTElDX01FVEFEQVRBX0tVQkVSTkVURVNfTkFNRVNQQUNFX05BTUUiLCJ2YWx1ZUZyb20iOnsiZmllbGRSZWYiOnsiZmllbGRQYXRoIjoibWV0YWRhdGEubmFtZXNwYWNlIn19fX0seyJvcCI6ImFkZCIsInBhdGgiOiIvc3BlYy9jb250YWluZXJzLzAvZW52Ly0iLCJ2YWx1ZSI6eyJuYW1lIjoiTkVXX1JFTElDX01FVEFEQVRBX0tVQkVSTkVURVNfUE9EX05BTUUiLCJ2YWx1ZUZyb20iOnsiZmllbGRSZWYiOnsiZmllbGRQYXRoIjoibWV0YWRhdGEubmFtZSJ9fX19LHsib3AiOiJhZGQiLCJwYXRoIjoiL3NwZWMvY29udGFpbmVycy8wL2Vudi8tIiwidmFsdWUiOnsibmFtZSI6Ik5FV19SRUxJQ19NRVRBREFUQV9LVUJFUk5FVEVTX0NPTlRBSU5FUl9OQU1FIiwidmFsdWUiOiJjMSJ9fSx7Im9wIjoiYWRkIiwicGF0aCI6Ii9zcGVjL2NvbnRhaW5lcnMvMC9lbnYvLSIsInZhbHVlIjp7Im5hbWUiOiJORVdfUkVMSUNfTUVUQURBVEFfS1VCRVJORVRFU19ERVBMT1lNRU5UX05BTUUiLCJ2YWx1ZSI6InRlc3QifX0seyJvcCI6ImFkZCIsInBhdGgiOiIvc3BlYy9jb250YWluZXJzLzEvZW52IiwidmFsdWUiOlt7Im5hbWUiOiJORVdfUkVMSUNfTUVUQURBVEFfS1VCRVJORVRFU19DTFVTVEVSX05BTUUiLCJ2YWx1ZSI6ImZvb2JhciJ9XX0seyJvcCI6ImFkZCIsInBhdGgiOiIvc3BlYy9jb250YWluZXJzLzEvZW52Ly0iLCJ2YWx1ZSI6eyJuYW1lIjoiTkVXX1JFTElDX01FVEFEQVRBX0tVQkVSTkVURVNfTk9ERV9OQU1FIiwidmFsdWVGcm9tIjp7ImZpZWxkUmVmIjp7ImZpZWxkUGF0aCI6InNwZWMubm9kZU5hbWUifX19fSx7Im9wIjoiYWRkIiwicGF0aCI6Ii9zcGVjL2NvbnRhaW5lcnMvMS9lbnYvLSIsInZhbHVlIjp7Im5hbWUiOiJORVdfUkVMSUNfTUVUQURBVEFfS1VCRVJORVRFU19OQU1FU1BBQ0VfTkFNRSIsInZhbHVlRnJvbSI6eyJmaWVsZFJlZiI6eyJmaWVsZFBhdGgiOiJtZXRhZGF0YS5uYW1lc3BhY2UifX19fSx7Im9wIjoiYWRkIiwicGF0aCI6Ii9zcGVjL2NvbnRhaW5lcnMvMS9lbnYvLSIsInZhbHVlIjp7Im5hbWUiOiJORVdfUkVMSUNfTUVUQURBVEFfS1VCRVJORVRFU19QT0RfTkFNRSIsInZhbHVlRnJvbSI6eyJmaWVsZFJlZiI6eyJmaWVsZFBhdGgiOiJtZXRhZGF0YS5uYW1lIn19fX0seyJvcCI6ImFkZCIsInBhdGgiOiIvc3BlYy9jb250YWluZXJzLzEvZW52Ly0iLCJ2YWx1ZSI6eyJuYW1lIjoiTkVXX1JFTElDX01FVEFEQVRBX0tVQkVSTkVURVNfQ09OVEFJTkVSX05BTUUiLCJ2YWx1ZSI6ImMyIn19LHsib3AiOiJhZGQiLCJwYXRoIjoiL3NwZWMvY29udGFpbmVycy8xL2Vudi8tIiwidmFsdWUiOnsibmFtZSI6Ik5FV19SRUxJQ19NRVRBREFUQV9LVUJFUk5FVEVTX0RFUExPWU1FTlRfTkFNRSIsInZhbHVlIjoidGVzdCJ9fV0= 78 | type: string 79 | patchType: 80 | description: The type of Patch. Currently only "JSONPatch" is allowed 81 | enum: 82 | - JSONPatch 83 | example: JSONPatch 84 | type: string 85 | status: 86 | $ref: '#/components/schemas/AdmissionResponseStatus' 87 | uid: 88 | description: >- 89 | Identifier for the individual request/response. This should be 90 | copied over from the corresponding AdmissionRequest 91 | type: string 92 | type: object 93 | AdmissionResponseStatus: 94 | description: Status is present in case the mutation was not executed. 95 | properties: 96 | message: 97 | description: >- 98 | A human-readable description of of why this operation is in the 99 | "Failure" status or was not "allowed". 100 | type: string 101 | reason: 102 | description: >- 103 | A machine-readable description of why this operation is in the 104 | "Failure" status or was not "allowed". If "message" is set then this 105 | value is not used. 106 | type: string 107 | type: object 108 | AdmissionReviewRequest: 109 | properties: 110 | request: 111 | $ref: '#/components/schemas/AdmissionRequest' 112 | required: 113 | - request 114 | type: object 115 | AdmissionReviewResponse: 116 | properties: 117 | response: 118 | $ref: '#/components/schemas/AdmissionResponse' 119 | required: 120 | - response 121 | type: object 122 | externalDocs: 123 | description: k8s-metadata-injection repository 124 | url: 'https://github.com/newrelic/k8s-metadata-injection' 125 | -------------------------------------------------------------------------------- /src/server/readiness_probe.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import "net/http" 4 | 5 | // TLSReadyReadinessProbe defines a readiness check for a Webhook struct based on the presence of its TLS certificate and key. 6 | // It requires the whole webhook as parameter to be able to RLock on the certificate for the presence confirmation. 7 | func TLSReadyReadinessProbe(webhook *Webhook) http.HandlerFunc { 8 | return func(w http.ResponseWriter, r *http.Request) { 9 | webhook.RLock() 10 | defer webhook.RUnlock() 11 | 12 | if webhook.Cert == nil { 13 | response := "Certificate not present" 14 | w.WriteHeader(503) 15 | if _, err := w.Write([]byte(response)); err != nil { 16 | webhook.Logger.Errorw("can't write response", "err", err, "response", response) 17 | } 18 | return 19 | } 20 | 21 | okResponse := "OK" 22 | if _, err := w.Write([]byte(okResponse)); err != nil { 23 | webhook.Logger.Errorw("can't write response", "err", err, "response", okResponse) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/server/readiness_probe_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "crypto/tls" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestTLSReadyReadinessProbe(t *testing.T) { 13 | cases := []struct { 14 | desc string 15 | certificate *tls.Certificate 16 | responseCode int 17 | }{ 18 | { 19 | desc: "certificate not present (bad health)", 20 | certificate: nil, 21 | responseCode: 503, 22 | }, 23 | { 24 | desc: "certificate present (good health)", 25 | certificate: &tls.Certificate{}, 26 | responseCode: 200, 27 | }, 28 | } 29 | 30 | webhook := Webhook{} 31 | healthCheck := http.HandlerFunc(TLSReadyReadinessProbe(&webhook)) 32 | server := httptest.NewServer(healthCheck) 33 | 34 | for _, c := range cases { 35 | t.Run(c.desc, func(t *testing.T) { 36 | webhook.Cert = c.certificate 37 | 38 | resp, err := http.Get(server.URL) 39 | 40 | assert.NoError(t, err) 41 | assert.Equal(t, c.responseCode, resp.StatusCode) 42 | }) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/server/testdata/expectedAdmissionReviewPatch.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "op": "add", 4 | "path": "/spec/containers/0/env", 5 | "value": [ 6 | { 7 | "name": "NEW_RELIC_METADATA_KUBERNETES_CLUSTER_NAME", 8 | "value": "foobar" 9 | } 10 | ] 11 | }, 12 | { 13 | "op": "add", 14 | "path": "/spec/containers/0/env/-", 15 | "value": { 16 | "name": "NEW_RELIC_METADATA_KUBERNETES_NODE_NAME", 17 | "valueFrom": { 18 | "fieldRef": { 19 | "fieldPath": "spec.nodeName" 20 | } 21 | } 22 | } 23 | }, 24 | { 25 | "op": "add", 26 | "path": "/spec/containers/0/env/-", 27 | "value": { 28 | "name": "NEW_RELIC_METADATA_KUBERNETES_NAMESPACE_NAME", 29 | "valueFrom": { 30 | "fieldRef": { 31 | "fieldPath": "metadata.namespace" 32 | } 33 | } 34 | } 35 | }, 36 | { 37 | "op": "add", 38 | "path": "/spec/containers/0/env/-", 39 | "value": { 40 | "name": "NEW_RELIC_METADATA_KUBERNETES_POD_NAME", 41 | "valueFrom": { 42 | "fieldRef": { 43 | "fieldPath": "metadata.name" 44 | } 45 | } 46 | } 47 | }, 48 | { 49 | "op": "add", 50 | "path": "/spec/containers/0/env/-", 51 | "value": { 52 | "name": "NEW_RELIC_METADATA_KUBERNETES_CONTAINER_NAME", 53 | "value": "c1" 54 | } 55 | }, 56 | { 57 | "op": "add", 58 | "path": "/spec/containers/0/env/-", 59 | "value": { 60 | "name": "NEW_RELIC_METADATA_KUBERNETES_CONTAINER_IMAGE_NAME", 61 | "value": "newrelic/image:latest" 62 | } 63 | }, 64 | { 65 | "op": "add", 66 | "path": "/spec/containers/0/env/-", 67 | "value": { 68 | "name": "NEW_RELIC_METADATA_KUBERNETES_DEPLOYMENT_NAME", 69 | "value": "test" 70 | } 71 | }, 72 | { 73 | "op": "add", 74 | "path": "/spec/containers/1/env", 75 | "value": [ 76 | { 77 | "name": "NEW_RELIC_METADATA_KUBERNETES_CLUSTER_NAME", 78 | "value": "foobar" 79 | } 80 | ] 81 | }, 82 | { 83 | "op": "add", 84 | "path": "/spec/containers/1/env/-", 85 | "value": { 86 | "name": "NEW_RELIC_METADATA_KUBERNETES_NODE_NAME", 87 | "valueFrom": { 88 | "fieldRef": { 89 | "fieldPath": "spec.nodeName" 90 | } 91 | } 92 | } 93 | }, 94 | { 95 | "op": "add", 96 | "path": "/spec/containers/1/env/-", 97 | "value": { 98 | "name": "NEW_RELIC_METADATA_KUBERNETES_NAMESPACE_NAME", 99 | "valueFrom": { 100 | "fieldRef": { 101 | "fieldPath": "metadata.namespace" 102 | } 103 | } 104 | } 105 | }, 106 | { 107 | "op": "add", 108 | "path": "/spec/containers/1/env/-", 109 | "value": { 110 | "name": "NEW_RELIC_METADATA_KUBERNETES_POD_NAME", 111 | "valueFrom": { 112 | "fieldRef": { 113 | "fieldPath": "metadata.name" 114 | } 115 | } 116 | } 117 | }, 118 | { 119 | "op": "add", 120 | "path": "/spec/containers/1/env/-", 121 | "value": { 122 | "name": "NEW_RELIC_METADATA_KUBERNETES_CONTAINER_NAME", 123 | "value": "c2" 124 | } 125 | }, 126 | { 127 | "op": "add", 128 | "path": "/spec/containers/1/env/-", 129 | "value": { 130 | "name": "NEW_RELIC_METADATA_KUBERNETES_CONTAINER_IMAGE_NAME", 131 | "value": "newrelic/image2:1.0.0" 132 | } 133 | }, 134 | { 135 | "op": "add", 136 | "path": "/spec/containers/1/env/-", 137 | "value": { 138 | "name": "NEW_RELIC_METADATA_KUBERNETES_DEPLOYMENT_NAME", 139 | "value": "test" 140 | } 141 | } 142 | ] 143 | -------------------------------------------------------------------------------- /src/server/webhook.go: -------------------------------------------------------------------------------- 1 | // Based on https://github.com/morvencao/kube-mutating-webhook-tutorial/ 2 | 3 | package server 4 | 5 | import ( 6 | "crypto/tls" 7 | "encoding/json" 8 | "fmt" 9 | "io" 10 | "net/http" 11 | "strings" 12 | "sync" 13 | 14 | "github.com/fsnotify/fsnotify" 15 | "go.uber.org/zap" 16 | admissionv1 "k8s.io/api/admission/v1" 17 | corev1 "k8s.io/api/core/v1" 18 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 19 | "k8s.io/apimachinery/pkg/runtime" 20 | "k8s.io/apimachinery/pkg/runtime/serializer" 21 | ) 22 | 23 | var ( 24 | runtimeScheme = runtime.NewScheme() 25 | codecs = serializer.NewCodecFactory(runtimeScheme) 26 | deserializer = codecs.UniversalDeserializer() 27 | ) 28 | 29 | var ignoredNamespaces = []string{ 30 | metav1.NamespaceSystem, 31 | metav1.NamespacePublic, 32 | } 33 | 34 | func createEnvVarFromFieldPath(envVarName, fieldPath string) corev1.EnvVar { 35 | return corev1.EnvVar{Name: envVarName, ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{FieldPath: fieldPath}}} 36 | } 37 | 38 | func createEnvVarFromString(envVarName, envVarValue string) corev1.EnvVar { 39 | return corev1.EnvVar{Name: envVarName, Value: envVarValue} 40 | } 41 | 42 | // getEnvVarsToInject returns the environment variables to inject in the given container 43 | func (whsvr *Webhook) getEnvVarsToInject(pod *corev1.Pod, container *corev1.Container) []corev1.EnvVar { 44 | vars := []corev1.EnvVar{ 45 | createEnvVarFromString("NEW_RELIC_METADATA_KUBERNETES_CLUSTER_NAME", whsvr.ClusterName), 46 | createEnvVarFromFieldPath("NEW_RELIC_METADATA_KUBERNETES_NODE_NAME", "spec.nodeName"), 47 | createEnvVarFromFieldPath("NEW_RELIC_METADATA_KUBERNETES_NAMESPACE_NAME", "metadata.namespace"), 48 | createEnvVarFromFieldPath("NEW_RELIC_METADATA_KUBERNETES_POD_NAME", "metadata.name"), 49 | createEnvVarFromString("NEW_RELIC_METADATA_KUBERNETES_CONTAINER_NAME", container.Name), 50 | createEnvVarFromString("NEW_RELIC_METADATA_KUBERNETES_CONTAINER_IMAGE_NAME", container.Image), 51 | } 52 | 53 | whsvr.Logger.Infow("creating env variables", "cluster_name", whsvr.ClusterName, "container_name", container.Name, "container_image", container.Image) 54 | // Guess the name of the deployment. We check whether the Pod is Owned by a ReplicaSet and confirms with the 55 | // naming convention for a Deployment. This can give a false positive if the user uses ReplicaSets directly. 56 | if len(pod.OwnerReferences) == 1 && pod.OwnerReferences[0].Kind == "ReplicaSet" { 57 | podParts := strings.Split(pod.GenerateName, "-") 58 | if len(podParts) >= 3 { 59 | deployment := strings.Join(podParts[:len(podParts)-2], "-") 60 | vars = append(vars, createEnvVarFromString("NEW_RELIC_METADATA_KUBERNETES_DEPLOYMENT_NAME", deployment)) 61 | } 62 | } 63 | 64 | return vars 65 | } 66 | 67 | // Webhook is a webhook server that can accept requests from the Apiserver 68 | type Webhook struct { 69 | sync.RWMutex 70 | CertFile string 71 | KeyFile string 72 | Cert *tls.Certificate 73 | ClusterName string 74 | Logger *zap.SugaredLogger 75 | Server *http.Server 76 | CertWatcher *fsnotify.Watcher 77 | } 78 | 79 | // GetCert returns the certificate that should be used by the server in the TLS handshake. 80 | func (whsvr *Webhook) GetCert(*tls.ClientHelloInfo) (*tls.Certificate, error) { 81 | whsvr.Lock() 82 | defer whsvr.Unlock() 83 | return whsvr.Cert, nil 84 | } 85 | 86 | type patchOperation struct { 87 | Op string `json:"op"` 88 | Path string `json:"path"` 89 | Value interface{} `json:"value,omitempty"` 90 | } 91 | 92 | // Check whether the target resource needs to be mutated 93 | func mutationRequired(ignoredList []string, metadata *metav1.ObjectMeta) bool { 94 | // skip special kubernetes system namespaces 95 | for _, namespace := range ignoredList { 96 | if metadata.Namespace == namespace { 97 | return false 98 | } 99 | } 100 | return true 101 | } 102 | 103 | func (whsvr *Webhook) updateContainer(pod *corev1.Pod, index int, container *corev1.Container) (patch []patchOperation) { 104 | // Create map with all environment variable names 105 | envVarMap := map[string]bool{} 106 | for _, envVar := range container.Env { 107 | envVarMap[envVar.Name] = true 108 | } 109 | 110 | // Create a patch for each EnvVar in toInject (if they are not yet defined on the container) 111 | first := len(envVarMap) == 0 112 | var value interface{} 113 | basePath := fmt.Sprintf("/spec/containers/%d/env", index) 114 | 115 | for _, inject := range whsvr.getEnvVarsToInject(pod, container) { 116 | if _, present := envVarMap[inject.Name]; !present { 117 | value = inject 118 | path := basePath 119 | 120 | if first { 121 | // For the first element we have to create the list 122 | value = []corev1.EnvVar{inject} 123 | first = false 124 | } else { 125 | // For the other elements we can append to the list 126 | path = path + "/-" 127 | } 128 | 129 | patch = append(patch, patchOperation{ 130 | Op: "add", 131 | Path: path, 132 | Value: value, 133 | }) 134 | } 135 | } 136 | return patch 137 | } 138 | 139 | // create mutation patch for resources 140 | func (whsvr *Webhook) createPatch(pod *corev1.Pod) ([]byte, error) { 141 | var patch []patchOperation 142 | 143 | for i, container := range pod.Spec.Containers { 144 | patch = append(patch, whsvr.updateContainer(pod, i, &container)...) 145 | } 146 | 147 | return json.Marshal(patch) 148 | } 149 | 150 | // main mutation process 151 | func (whsvr *Webhook) mutate(ar *admissionv1.AdmissionReview) ([]byte, error) { 152 | req := ar.Request 153 | var pod corev1.Pod 154 | if err := json.Unmarshal(req.Object.Raw, &pod); err != nil { 155 | whsvr.Logger.Errorw("could not unmarshal raw object", "err", err, "object", string(req.Object.Raw)) 156 | return nil, err 157 | } 158 | 159 | whsvr.Logger.Infow("received admission review", "kind", req.Kind, "namespace", req.Namespace, "name", 160 | req.Name, "pod", pod.Name, "UID", req.UID, "operation", req.Operation, "userinfo", req.UserInfo) 161 | 162 | // determine whether to perform mutation 163 | if !mutationRequired(ignoredNamespaces, &pod.ObjectMeta) { 164 | whsvr.Logger.Infow("skipped mutation", "namespace", pod.Namespace, "pod", pod.Name, "reason", "policy check (special namespaces)") 165 | return nil, nil 166 | } 167 | 168 | patchBytes, err := whsvr.createPatch(&pod) 169 | if err != nil { 170 | return nil, err 171 | } 172 | 173 | whsvr.Logger.Infow("admission response created", "response", string(patchBytes)) 174 | return patchBytes, nil 175 | } 176 | 177 | // Serve method for webhook server 178 | func (whsvr *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) { 179 | var body []byte 180 | 181 | if whsvr.Logger == nil { 182 | whsvr.Logger = zap.NewNop().Sugar() 183 | } 184 | 185 | if r.Body != nil { 186 | if data, err := io.ReadAll(r.Body); err == nil { 187 | body = data 188 | } 189 | } 190 | if len(body) == 0 { 191 | whsvr.Logger.Error("empty body") 192 | http.Error(w, "empty body", http.StatusBadRequest) 193 | return 194 | } 195 | 196 | // verify the content type is accurate 197 | contentType := r.Header.Get("Content-Type") 198 | if contentType != "application/json" { 199 | whsvr.Logger.Errorw("invalid content type", "expected", "application/json", "context type", contentType) 200 | http.Error(w, "invalid Content-Type, expect `application/json`", http.StatusUnsupportedMediaType) 201 | return 202 | } 203 | 204 | admissionReviewRequest := admissionv1.AdmissionReview{} 205 | if _, _, err := deserializer.Decode(body, nil, &admissionReviewRequest); err != nil { 206 | whsvr.Logger.Errorw("can't decode body", "err", err, "body", body) 207 | http.Error(w, fmt.Sprintf("could not decode request body: %q", err.Error()), http.StatusBadRequest) 208 | return 209 | } 210 | 211 | if len(admissionReviewRequest.Request.Object.Raw) == 0 { 212 | whsvr.Logger.Errorw("object not present in request body", "body", body) 213 | http.Error(w, fmt.Sprintf("object not present in request body: %q", body), http.StatusBadRequest) 214 | return 215 | } 216 | 217 | patch, err := whsvr.mutate(&admissionReviewRequest) 218 | if err != nil { 219 | whsvr.Logger.Errorw("error during mutation", "err", err) 220 | http.Error(w, fmt.Sprintf("error during mutation: %q", err.Error()), http.StatusInternalServerError) 221 | return 222 | } 223 | 224 | switch admissionReviewRequest.APIVersion { 225 | // Types should be backward compatible between v1 and v1beta1 according to 226 | // https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#webhook-request-and-response. 227 | case "admission.k8s.io/v1", "admission.k8s.io/v1beta1": 228 | default: 229 | whsvr.Logger.Errorw("unsupported admission API Version request", "version", admissionReviewRequest.APIVersion) 230 | http.Error(w, fmt.Sprintf("unsupported admission API Version request: %q", admissionReviewRequest.APIVersion), http.StatusBadRequest) 231 | return 232 | } 233 | 234 | admissionReviewResponse := admissionv1.AdmissionReview{ 235 | TypeMeta: metav1.TypeMeta{ 236 | Kind: admissionReviewRequest.Kind, 237 | APIVersion: admissionReviewRequest.APIVersion, 238 | }, 239 | Response: &admissionv1.AdmissionResponse{ 240 | Allowed: true, // Always allow the creation of the pod since this webhook does not act as Validating Webhook. 241 | }, 242 | } 243 | 244 | if len(patch) > 0 { 245 | admissionReviewResponse.Response.Patch = patch 246 | admissionReviewResponse.Response.PatchType = func() *admissionv1.PatchType { 247 | pt := admissionv1.PatchTypeJSONPatch // Only PatchTypeJSONPatch is allowed by now. 248 | return &pt 249 | }() 250 | } 251 | 252 | if admissionReviewRequest.Request != nil { 253 | admissionReviewResponse.Response.UID = admissionReviewRequest.Request.UID 254 | } 255 | 256 | resp, err := json.Marshal(admissionReviewResponse) 257 | if err != nil { 258 | whsvr.Logger.Errorw("can't decode response", "err", err) 259 | http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError) 260 | return 261 | } 262 | whsvr.Logger.Info("writing response") 263 | if _, err := w.Write(resp); err != nil { 264 | whsvr.Logger.Errorw("can't write response", "err", err) 265 | http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError) 266 | return 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/server/webhook_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "os" 9 | 10 | "net/http" 11 | "net/http/httptest" 12 | "testing" 13 | 14 | "github.com/stretchr/testify/assert" 15 | admissionv1 "k8s.io/api/admission/v1" 16 | corev1 "k8s.io/api/core/v1" 17 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 18 | "k8s.io/apimachinery/pkg/runtime" 19 | "k8s.io/apimachinery/pkg/types" 20 | ) 21 | 22 | func TestServeHTTP(t *testing.T) { 23 | patchForValidBody, err := os.ReadFile("testdata/expectedAdmissionReviewPatch.json") 24 | if err != nil { 25 | t.Fatalf("cannot read testdata file: %v", err) 26 | } 27 | var expectedPatchForValidBody bytes.Buffer 28 | if len(patchForValidBody) > 0 { 29 | if err := json.Compact(&expectedPatchForValidBody, patchForValidBody); err != nil { 30 | t.Fatal(err.Error()) 31 | } 32 | } 33 | 34 | missingObjectRequestBody := bytes.Replace(makeTestData(t, "default"), []byte("\"object\""), []byte("\"foo\""), -1) 35 | 36 | patchTypeForValidBody := admissionv1.PatchTypeJSONPatch 37 | cases := []struct { 38 | name string 39 | requestBody []byte 40 | contentType string 41 | expectedStatusCode int 42 | expectedBodyWhenHTTPError string 43 | expectedAdmissionReview admissionv1.AdmissionReview 44 | }{ 45 | { 46 | name: "mutation applied - valid body", 47 | requestBody: makeTestData(t, "default"), 48 | contentType: "application/json", 49 | expectedStatusCode: http.StatusOK, 50 | expectedAdmissionReview: admissionv1.AdmissionReview{ 51 | TypeMeta: metav1.TypeMeta{ 52 | Kind: "AdmissionReview", 53 | APIVersion: "admission.k8s.io/v1", 54 | }, 55 | Response: &admissionv1.AdmissionResponse{ 56 | UID: types.UID("1"), 57 | Allowed: true, 58 | Result: nil, 59 | Patch: expectedPatchForValidBody.Bytes(), 60 | PatchType: &patchTypeForValidBody, 61 | }, 62 | }, 63 | }, 64 | { 65 | name: "mutation not applied - valid body for ignored namespaces", 66 | requestBody: makeTestData(t, "kube-system"), 67 | contentType: "application/json", 68 | expectedStatusCode: http.StatusOK, 69 | expectedAdmissionReview: admissionv1.AdmissionReview{ 70 | TypeMeta: metav1.TypeMeta{ 71 | Kind: "AdmissionReview", 72 | APIVersion: "admission.k8s.io/v1", 73 | }, 74 | Response: &admissionv1.AdmissionResponse{ 75 | UID: types.UID("1"), 76 | Allowed: true, 77 | Result: nil, 78 | Patch: nil, 79 | PatchType: nil, 80 | }, 81 | }, 82 | }, 83 | { 84 | name: "empty body", 85 | contentType: "application/json", 86 | expectedStatusCode: http.StatusBadRequest, 87 | expectedBodyWhenHTTPError: "empty body" + "\n", 88 | }, 89 | { 90 | name: "wrong content-type", 91 | requestBody: makeTestData(t, "default"), 92 | contentType: "application/yaml", 93 | expectedStatusCode: http.StatusUnsupportedMediaType, 94 | expectedBodyWhenHTTPError: "invalid Content-Type, expect `application/json`" + "\n", 95 | }, 96 | { 97 | name: "invalid body", 98 | requestBody: []byte{0, 1, 2}, 99 | contentType: "application/json", 100 | expectedStatusCode: http.StatusBadRequest, 101 | expectedBodyWhenHTTPError: "could not decode request body: \"yaml: control characters are not allowed\"\n", 102 | }, 103 | { 104 | name: "mutation fails - object not present in request body", 105 | requestBody: missingObjectRequestBody, 106 | contentType: "application/json", 107 | expectedStatusCode: http.StatusBadRequest, 108 | expectedBodyWhenHTTPError: fmt.Sprintf("object not present in request body: %q\n", missingObjectRequestBody), 109 | }, 110 | } 111 | 112 | whsvr := &Webhook{ 113 | ClusterName: "foobar", 114 | Server: &http.Server{}, 115 | } 116 | 117 | server := httptest.NewServer(whsvr) 118 | defer server.Close() 119 | 120 | for i, c := range cases { 121 | t.Run(fmt.Sprintf("[%d] %s", i, c.name), func(t *testing.T) { 122 | resp, err := http.Post(server.URL, c.contentType, bytes.NewReader(c.requestBody)) 123 | assert.NoError(t, err) 124 | assert.Equal(t, c.expectedStatusCode, resp.StatusCode) 125 | 126 | gotBody, err := io.ReadAll(resp.Body) 127 | if err != nil { 128 | t.Fatalf("could not read body: %v", err) 129 | } 130 | var gotReview admissionv1.AdmissionReview 131 | if err := json.Unmarshal(gotBody, &gotReview); err != nil { 132 | assert.Equal(t, c.expectedBodyWhenHTTPError, string(gotBody)) 133 | return 134 | } 135 | 136 | assert.Equal(t, c.expectedAdmissionReview, gotReview) 137 | }) 138 | } 139 | } 140 | 141 | func Benchmark_WebhookPerformance(b *testing.B) { 142 | body := makeTestData(b, "default") 143 | 144 | whsvr := &Webhook{ 145 | ClusterName: "foobar", 146 | Server: &http.Server{ 147 | Addr: ":8080", 148 | }, 149 | } 150 | 151 | server := httptest.NewServer(whsvr) 152 | defer server.Close() 153 | 154 | b.ResetTimer() 155 | for i := 0; i < b.N; i++ { 156 | http.Post(server.URL, "application/json", bytes.NewReader(body)) //nolint: errcheck 157 | } 158 | } 159 | 160 | func makeTestData(t testing.TB, namespace string) []byte { 161 | t.Helper() 162 | 163 | pod := corev1.Pod{ 164 | ObjectMeta: metav1.ObjectMeta{ 165 | Name: "test-123-123", 166 | GenerateName: "test-123-123", // required for creating metadata for deployment 167 | Annotations: map[string]string{}, 168 | Namespace: namespace, 169 | OwnerReferences: []metav1.OwnerReference{{Kind: "ReplicaSet"}}, // required for populating metadata for deployment 170 | }, 171 | Spec: corev1.PodSpec{ 172 | Volumes: []corev1.Volume{{Name: "v0"}}, 173 | InitContainers: []corev1.Container{{Name: "c0"}}, 174 | Containers: []corev1.Container{{Name: "c1", Image: "newrelic/image:latest"}, {Name: "c2", Image: "newrelic/image2:1.0.0"}}, 175 | ImagePullSecrets: []corev1.LocalObjectReference{{Name: "p0"}}, 176 | }, 177 | } 178 | 179 | raw, err := json.Marshal(&pod) 180 | if err != nil { 181 | t.Fatalf("Could not create test pod: %v", err) 182 | } 183 | 184 | review := admissionv1.AdmissionReview{ 185 | TypeMeta: metav1.TypeMeta{ 186 | Kind: "AdmissionReview", 187 | APIVersion: "admission.k8s.io/v1", 188 | }, 189 | Request: &admissionv1.AdmissionRequest{ 190 | Kind: metav1.GroupVersionKind{}, 191 | Object: runtime.RawExtension{ 192 | Raw: raw, 193 | }, 194 | Operation: admissionv1.Create, 195 | UID: types.UID("1"), 196 | }, 197 | } 198 | reviewJSON, err := json.Marshal(review) 199 | if err != nil { 200 | t.Fatalf("Failed to create AdmissionReview: %v", err) 201 | } 202 | return reviewJSON 203 | } 204 | --------------------------------------------------------------------------------