Skip to content
Open
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
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @Workable/systems
153 changes: 102 additions & 51 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
name: CI
name: External Secrets Workable CI

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

env:
# Common versions
GOLANGCI_VERSION: 'v2.4.0'
KUBERNETES_VERSION: '1.33.x'

# Sonar
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

permissions:
contents: read

Expand All @@ -38,9 +37,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 @@ -51,8 +51,25 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Check License Headers
uses: apache/skywalking-eyes/header@61275cc80d0798a405cb070f7d3a8aaf7cf2c2c1 # v0.8.0
with:
persist-credentials: false

- name: Setup Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
id: setup-go
with:
go-version-file: "go.mod"

- 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: ${{ env.GOLANGCI_VERSION }}
skip-pkg-cache: true
skip-build-cache: true

check-diff:
runs-on: ubuntu-latest
Expand All @@ -67,6 +84,8 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: hashicorp/setup-terraform@93d5a27adc7ac26085422db72568ae486f091042 # v3
- name: Setup Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
Expand Down Expand Up @@ -102,6 +121,8 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false

- name: Fetch History
run: git fetch --prune --unshallow
Expand All @@ -125,48 +146,78 @@ jobs:
run: |
make test

- name: Publish Unit Test Coverage
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
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
Comment thread
mkousteris marked this conversation as resolved.
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@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.1
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
run: |
docker tag Workable/external-secrets:${{ steps.container-info.outputs.image-tag }} \
${{ env.REGISTRY }}/external-secrets:${{ steps.container-info.outputs.image-tag }}
docker push ${{ env.REGISTRY }}/external-secrets:${{ steps.container-info.outputs.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 ppc64le"
build-platform: "linux/amd64,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
run: |
docker tag Workable/external-secrets:${{ steps.container-info.outputs.image-tag }} \
${{ env.REGISTRY }}/external-secrets:${{ steps.container-info.outputs.image-tag }}
docker push ${{ env.REGISTRY }}/external-secrets:${{ steps.container-info.outputs.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
run: |
docker tag Workable/external-secrets:${{ steps.container-info.outputs.image-tag }} \
${{ env.REGISTRY }}/external-secrets:${{ steps.container-info.outputs.image-tag }}
docker push ${{ env.REGISTRY }}/external-secrets:${{ steps.container-info.outputs.image-tag }}
9 changes: 2 additions & 7 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.
@go mod tidy
@cd e2e/ && go mod tidy
@cd apis/ && go mod tidy
Expand All @@ -82,16 +82,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
@test -z "$$(git status --porcelain)" || (echo "$$(git status --porcelain)" && $(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 Expand Up @@ -442,7 +437,7 @@ CTY_VERSION := 1.1.3
.PHONY: envtest
envtest: $(ENVTEST) ## Download envtest-setup locally if necessary.
$(ENVTEST): $(LOCALBIN)
test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest
test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@release-0.20

.PHONY: golangci-lint
.PHONY: $(GOLANGCI_LINT)
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 @@ -72,6 +72,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