Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 1 addition & 67 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,67 +1 @@
# External Secrets CODEOWNERS
#
# Maps repository paths to GitHub teams for review. Syntax reference:
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
#
# Order matters: the LAST matching pattern wins. The project-wide default
# therefore has to come first, with narrower paths after it.
#
# Teams need explicit write access to the repository, and a team that does not
# exist makes its line a no-op, so keep this file in step with org teams.

# --- Project-wide default (must stay first) ---
* @external-secrets/maintainers @external-secrets/interim-maintainers

# --- CI / Infrastructure ---
.github/ @external-secrets/ci-reviewers
hack/ @external-secrets/ci-reviewers @external-secrets/testing-reviewers

# --- Testing ---
tests/ @external-secrets/testing-reviewers
e2e/ @external-secrets/testing-reviewers

# --- Documentation ---
docs/ @external-secrets/docs-maintainers

# --- Core Controllers ---
apis/ @external-secrets/core-reviewers
pkg/controllers/ @external-secrets/core-reviewers

# --- Providers ---
providers/v1/ @external-secrets/providers-reviewers
providers/v1/akeyless/ @external-secrets/provider-akeyless-reviewers
providers/v1/aws/ @external-secrets/provider-aws-reviewers
providers/v1/azure/ @external-secrets/provider-azure-reviewers
providers/v1/barbican/ @external-secrets/provider-barbican-reviewers
providers/v1/beyondtrust/ @external-secrets/provider-beyondtrust-reviewers
providers/v1/bitwarden/ @external-secrets/provider-bitwarden-reviewers
providers/v1/chef/ @external-secrets/provider-chef-reviewers
providers/v1/cloudru/ @external-secrets/provider-cloudru-reviewers
providers/v1/conjur/ @external-secrets/provider-conjur-reviewers
providers/v1/delinea/ @external-secrets/provider-delinea-reviewers
providers/v1/doppler/ @external-secrets/provider-doppler-reviewers
providers/v1/fake/ @external-secrets/provider-fake-reviewers
providers/v1/fortanix/ @external-secrets/provider-fortanix-reviewers
providers/v1/gcp/ @external-secrets/provider-gcp-reviewers
providers/v1/github/ @external-secrets/provider-github-reviewers
providers/v1/gitlab/ @external-secrets/provider-gitlab-reviewers
providers/v1/ibm/ @external-secrets/provider-ibm-reviewers
providers/v1/infisical/ @external-secrets/provider-infisical-reviewers
providers/v1/keepersecurity/ @external-secrets/provider-keepersecurity-reviewers
providers/v1/kubernetes/ @external-secrets/provider-kubernetes-reviewers
providers/v1/onboardbase/ @external-secrets/provider-onboardbase-reviewers
providers/v1/onepassword/ @external-secrets/provider-onepassword-reviewers
providers/v1/onepasswordsdk/ @external-secrets/provider-onepasswordsdk-reviewers
providers/v1/openbao/ @external-secrets/provider-openbao-reviewers
providers/v1/oracle/ @external-secrets/provider-oracle-reviewers
providers/v1/passbolt/ @external-secrets/provider-passbolt-reviewers
providers/v1/passworddepot/ @external-secrets/provider-passworddepot-reviewers
providers/v1/previder/ @external-secrets/provider-previder-reviewers
providers/v1/pulumi/ @external-secrets/provider-pulumi-reviewers
providers/v1/scaleway/ @external-secrets/provider-scaleway-reviewers
providers/v1/secretserver/ @external-secrets/provider-secretserver-reviewers
providers/v1/senhasegura/ @external-secrets/provider-senhasegura-reviewers
providers/v1/vault/ @external-secrets/provider-vault-reviewers
providers/v1/webhook/ @external-secrets/provider-webhook-reviewers
providers/v1/yandex/ @external-secrets/provider-yandex-reviewers

* @Workable/systems
146 changes: 98 additions & 48 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
name: CI
name: External Secrets Workable CI

on:
push:
tags:
- workable-*
pull_request:
branches:
- main
pull_request: {}
- workable-*

env:
# Common versions
Expand Down Expand Up @@ -34,9 +36,10 @@ jobs:
do_not_skip: '["workflow_dispatch", "schedule", "push"]'
concurrent_skipping: false

license-check:
lint:
permissions:
contents: read
contents: read # for actions/checkout to fetch code
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
runs-on: ubuntu-latest
needs: detect-noop
if: needs.detect-noop.outputs.noop != 'true' && github.ref != 'refs/heads/main'
Expand All @@ -49,8 +52,24 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Check License Headers
uses: apache/skywalking-eyes/header@61275cc80d0798a405cb070f7d3a8aaf7cf2c2c1 # v0.8.0

- name: Setup Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
id: setup-go
with:
go-version-file: "go.mod"
cache: false

- name: Download Go modules
if: ${{ steps.setup-go.outputs.cache-hit != 'true' }}
run: go mod download

- name: Lint
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
with:
version: v2.12.2 # matches github.com/golangci/golangci-lint/v2 in hack/tools/go.mod
skip-pkg-cache: true
skip-build-cache: true

check-diff:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -127,47 +146,78 @@ jobs:
run: |
make test

- name: Publish Unit Test Coverage
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
publish-artifacts:
needs: [lint, check-diff, unit-tests]
if: ${{ needs.detect-noop.outputs.noop != 'true' && startsWith(github.ref, 'refs/tags/workable-') }}
permissions:
id-token: write
contents: read
runs-on: ubuntu-latest
environment: Workable
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false

- name: Get image tag
id: container-info
run: |
echo "image-tag=${GITHUB_REF#refs/tags/workable-}" >> $GITHUB_OUTPUT

- name: Build image
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
file: Dockerfile.standalone
push: false
tags: Workable/external-secrets:${{ steps.container-info.outputs.image-tag }}
provenance: false

# DISTRIBUTION OF SRE IMAGE
- name: Login to sre registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
registry: us-docker.pkg.dev
username: _json_key
password: ${{ secrets.SRE_GCR_SA }}

- name: Push image to sre registry
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
REGISTRY: us-docker.pkg.dev/sre-artifacts-20e4/gcr.io
IMAGE_TAG: ${{ steps.container-info.outputs.image-tag }}
run: |
docker tag "Workable/external-secrets:${IMAGE_TAG}" "${REGISTRY}/external-secrets:${IMAGE_TAG}"
docker push "${REGISTRY}/external-secrets:${IMAGE_TAG}"

# DISTRIBUTION OF STAGING IMAGE
- name: Login to staging registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
flags: unittests
file: ./cover.out
registry: us-docker.pkg.dev
username: _json_key
password: ${{ secrets.STAGING_GCR_SA }}

publish-artifacts:
needs: detect-noop
if: needs.detect-noop.outputs.noop != 'true'
uses: ./.github/workflows/publish.yml
permissions:
contents: read #actions/checkout
packages: write #for publishing artifacts
id-token: write #for keyless sign
strategy:
matrix:
include:
- dockerfile: "Dockerfile"
build-args: "CGO_ENABLED=0"
build-arch: "amd64 arm64 s390x ppc64le"
build-platform: "linux/amd64,linux/arm64,linux/s390x,linux/ppc64le"
tag-suffix: "" # distroless
- dockerfile: "Dockerfile.ubi"
build-args: "CGO_ENABLED=0"
build-arch: "amd64 arm64 ppc64le"
build-platform: "linux/amd64,linux/arm64,linux/ppc64le"
tag-suffix: "-ubi"
- dockerfile: "Dockerfile.ubi"
build-args: "CGO_ENABLED=0 GOEXPERIMENT=boringcrypto"
build-arch: "amd64 arm64 ppc64le"
build-platform: "linux/amd64,linux/arm64,linux/ppc64le"
tag-suffix: "-ubi-boringssl"
with:
dockerfile: ${{ matrix.dockerfile }}
tag-suffix: ${{ matrix.tag-suffix }}
image-name: ghcr.io/${{ github.repository }}
build-platform: ${{ matrix.build-platform }}
build-args: ${{ matrix.build-args }}
build-arch: ${{ matrix.build-arch }}
ref: ${{ github.ref }}
secrets:
IS_FORK: ${{ secrets.GHCR_USERNAME }} # this is just a secret to verify it is a fork or not, no other utility
- name: Push image to staging registry
env:
REGISTRY: us-docker.pkg.dev/staging-artifacts-786a/gcr.io
IMAGE_TAG: ${{ steps.container-info.outputs.image-tag }}
run: |
docker tag "Workable/external-secrets:${IMAGE_TAG}" "${REGISTRY}/external-secrets:${IMAGE_TAG}"
docker push "${REGISTRY}/external-secrets:${IMAGE_TAG}"

# DISTRIBUTION OF PRODUCTION IMAGE
- name: Login to production registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
registry: us-docker.pkg.dev
username: _json_key
password: ${{ secrets.PRODUCTION_GCR_SA }}

- name: Push image to production registry
env:
REGISTRY: us-docker.pkg.dev/production-artifacts-0b0d/gcr.io
IMAGE_TAG: ${{ steps.container-info.outputs.image-tag }}
run: |
docker tag "Workable/external-secrets:${IMAGE_TAG}" "${REGISTRY}/external-secrets:${IMAGE_TAG}"
docker push "${REGISTRY}/external-secrets:${IMAGE_TAG}"
5 changes: 4 additions & 1 deletion Dockerfile.standalone
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
# This version of Dockerfile is for building without external dependencies.
# Build a multi-platform image e.g. `docker buildx build --push --platform linux/arm64,linux/amd64 --tag external-secrets:dev --file Dockerfile.standalone .`
# Providers are gated behind Go build tags (see pkg/register/<name>.go and AGENTS.md); pass
# --build-arg PROVIDER=<name>[,<name>...] to build a slimmer binary with only the providers you need.
FROM golang:1.27.0-alpine@sha256:4c9fe60190a2a3350ddc51de80d0224b8a6698d12bdfc999fee45ea9d6c46dbc AS builder
# Add metadata
LABEL maintainer="cncf-externalsecretsop-maintainers@lists.cncf.io" \
description="External Secrets Operator is a Kubernetes operator that integrates external secret management systems"
ARG TARGETOS
ARG TARGETARCH
ARG PROVIDER=all_providers
ENV CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH}
WORKDIR /app
COPY . /app/
RUN go mod download
RUN go build -o external-secrets main.go
RUN go build -tags ${PROVIDER} -o external-secrets main.go

FROM gcr.io/distroless/static@sha256:f2ea2709ac8db56323cbd7d014277f32cb572d9ea124b0076f7aafe5980678fe AS app
COPY --from=builder /app/external-secrets /bin/external-secrets
Expand Down
7 changes: 1 addition & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ FAIL = (echo ${TIME} ${RED}[FAIL]${CNone} && false)
# ====================================================================================
# Conformance

reviewable: generate docs manifests helm.generate helm.schema.update helm.docs lint license.check helm.test.update test.crds.update tf.fmt ## Ensure a PR is ready for review.
reviewable: generate docs manifests helm.generate helm.schema.update helm.docs lint helm.test.update test.crds.update tf.fmt ## Ensure a PR is ready for review.
@GOWORK=off go -C hack/tools mod tidy # Tools and their deps are not to be included in project's GOWORK or in the project go.mod/sum. We consider them external.
@GOWORK=off go -C hack/tools/gen-crd-api-reference-docs mod tidy
@go mod tidy
Expand All @@ -84,16 +84,11 @@ reviewable: generate docs manifests helm.generate helm.schema.update helm.docs l

check-diff: reviewable ## Ensure branch is clean.
@$(INFO) checking that branch is clean
@status="$$(git status --porcelain)" && test -z "$$status" || (printf '%s\n' "$$status" && $(FAIL))
@$(OK) branch is clean

update-deps: ## Update dependencies across all modules (root, apis, runtime, e2e, providers, generators)
@./hack/update-deps.sh

.PHONY: license.check
license.check:
$(DOCKER) run --rm -u $(shell id -u) -v $(shell pwd):/github/workspace apache/skywalking-eyes:0.6.0 header check

# ====================================================================================
# Golang

Expand Down
6 changes: 6 additions & 0 deletions providers/v1/vault/client_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ func (c *client) GetSecret(ctx context.Context, ref esv1.ExternalSecretDataRemot
}
}

// Replace symlinks
data, err = c.resolveSymlink(ctx, data)
if err != nil {
return nil, err
}

return getSecretValue(data, ref.Property)
}

Expand Down
76 changes: 76 additions & 0 deletions providers/v1/vault/symlink.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package vault

import (
"context"
"regexp"
"strings"
)

const (
// Symlink must start with the prefix vault:// to be valid.
vaultSymlink = `vault://`
// Path can be anything and matches the last # as a separator of key.
vaultSymlinkPath = `(?P<Path>.*)#`
// Key can be any alphanumeric character and stops with the first @.
vaultSymlinkSecret = `(?P<Secret>\w+)`
// Version is optional and will match any number after @.
vaultSymlinkVersion = `(@(?P<Version>\d+)?)?`
vaultSymlinkPattern = vaultSymlink + vaultSymlinkPath + vaultSymlinkSecret + vaultSymlinkVersion
)

// isSymlink tests if secret can be converted to string and if it matches the symlink pattern.
func isSymlink(secret any) bool {
if s, ok := secret.(string); ok {
return strings.HasPrefix(s, vaultSymlink)
}

return false
}

// extractSymlinkParts extract capture group items of regex to a map.
func extractSymlinkParts(secret any) (paramsMap map[string]string) {
r := regexp.MustCompile(vaultSymlinkPattern)
match := r.FindStringSubmatch(secret.(string))
paramsMap = make(map[string]string)

for i, name := range r.SubexpNames() {
if i > 0 && i <= len(match) {
paramsMap[name] = match[i]
}
}

return paramsMap
}

// resolveSymlink test if the data passed has symlinks and resolve them.
func (c *client) resolveSymlink(ctx context.Context, data map[string]any) (map[string]any, error) {
for key, secret := range data {
for isSymlink(secret) {
symlink := extractSymlinkParts(secret)

s, err := c.readSecret(ctx, symlink["Path"], symlink["Version"])
if err != nil {
return nil, err
}

secret = s[symlink["Secret"]]
data[key] = secret
}
}

return data, nil
}
Loading
Loading