Skip to content

Commit

Permalink
Enable Istio-style SPIFFE IDs; custom namespaces, and trust domains (#…
Browse files Browse the repository at this point in the history
…1011)

* testing with custom manifests

Signed-off-by: Volkan Özçelik <[email protected]>

* env var change

Signed-off-by: Volkan Özçelik <[email protected]>

* updated trust domain of SCM.

Signed-off-by: Volkan Özçelik <[email protected]>

* manifest update

Signed-off-by: Volkan Özçelik <[email protected]>

* add SPIFFE trust domain to charts

Signed-off-by: Volkan Özçelik <[email protected]>

* add diagnostic code

Signed-off-by: Volkan Özçelik <[email protected]>

* add diagnostic code

Signed-off-by: Volkan Özçelik <[email protected]>

* manifest and business logic update

Signed-off-by: Volkan Özçelik <[email protected]>

* more diagnostics

Signed-off-by: Volkan Özçelik <[email protected]>

* more diagnostic

Signed-off-by: Volkan Özçelik <[email protected]>

* more diagnostics

Signed-off-by: Volkan Özçelik <[email protected]>

* typo fix

Signed-off-by: Volkan Özçelik <[email protected]>

* manifest update

Signed-off-by: Volkan Özçelik <[email protected]>

* more diagnostics

Signed-off-by: Volkan Özçelik <[email protected]>

* manifest update

Signed-off-by: Volkan Özçelik <[email protected]>

* manifest update

Signed-off-by: Volkan Özçelik <[email protected]>

* Add missing route.

Signed-off-by: Volkan Özçelik <[email protected]>

* manifest changes and validation fixes

Signed-off-by: Volkan Özçelik <[email protected]>

* manifest updates

Signed-off-by: Volkan Özçelik <[email protected]>

* debug code

Signed-off-by: Volkan Özçelik <[email protected]>

* more debug code

Signed-off-by: Volkan Özçelik <[email protected]>

* manifest fix

Signed-off-by: Volkan Özçelik <[email protected]>

* manifest update

Signed-off-by: Volkan Özçelik <[email protected]>

* manifest update

Signed-off-by: Volkan Özçelik <[email protected]>

* Fix failing tests

Signed-off-by: Volkan Özçelik <[email protected]>

* test fix

Signed-off-by: Volkan Özçelik <[email protected]>

---------

Signed-off-by: Volkan Özçelik <[email protected]>
  • Loading branch information
v0lkan authored Jun 25, 2024
1 parent 1aa876f commit 2ac30f9
Show file tree
Hide file tree
Showing 27 changed files with 786 additions and 292 deletions.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ VSECM_EKS_REGISTRY_URL ?= "public.ecr.aws/h8y1n7y7"
VSECM_NAMESPACE_SYSTEM ?= "vsecm-system"
VSECM_NAMESPACE_SPIRE ?= "spire-system"
VSECM_NAMESPACE_SPIRE_SERVER ?= "spire-server"
# VSECM_NAMESPACE_SYSTEM ?= "vsecm-system-custom"
# VSECM_NAMESPACE_SPIRE ?= "spire-system-custom"
# VSECM_NAMESPACE_SPIRE_SERVER ?= "spire-server-custom"

# Utils
include ./makefiles/VSecMMacOs.mk
Expand Down
2 changes: 1 addition & 1 deletion app/safe/internal/bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ func AcquireSource(
if !validation.IsSafe(svidId.String()) {
log.FatalLn(
cid,
"SpiffeId check: I don't know you, and it's crazy:",
"SpiffeId check: Safe:bootstrap: I don't know you, and it's crazy:",
svidId.String(),
)
return nil
Expand Down
19 changes: 19 additions & 0 deletions app/safe/internal/server/handle/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,48 @@ type handler func(string, *http.Request, http.ResponseWriter)

func factory(p, m string) handler {
switch {
// Route to fetch the Keystone status.
// The status can be "pending" or "ready".
case m == http.MethodGet && p == url.SentinelKeystone:
return routeKeystone.Status

// Route to return the secrets list. The values of the
// secrets are encrypted.
case m == http.MethodGet && p == url.SentinelSecretsWithReveal:
return routeList.Encrypted

// Route to return the secrets list. This route only displays names
// and metadata of the secrets. The values will not be provided.
case m == http.MethodGet && p == url.SentinelSecrets:
return routeList.Masked

// Route to upsert a secret.
case m == http.MethodPost && p == url.SentinelSecrets:
return routeSecret.Secret

// Route to delete secrets from VSecM Safe.
// Only VSecM Sentinel is allowed to call this API endpoint.
// Calling it from anywhere else will error out.
case m == http.MethodDelete && p == url.SentinelSecrets:
return routeDelete.Delete

// Route to define the root key.
// Only VSecM Sentinel is allowed to call this API endpoint.
case m == http.MethodPost && p == url.SentinelKeys:
return routeReceive.Keys

// Route to fetch secrets.
// Only a VSecM-nominated workload is allowed to
// call this API endpoint. Calling it from anywhere else will
// error out.
case m == http.MethodGet && p == url.WorkloadSecrets:
return routeFetch.Fetch

// Route to post secrets from the workload.
case m == http.MethodPost && p == url.WorkloadSecrets:
panic("routeWorkloadPostSecrets not implemented")

// Fallback route.
default:
return routeFallback.Fallback
}
Expand Down
19 changes: 12 additions & 7 deletions app/safe/internal/server/route/base/extract/extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,10 @@ package extract

import (
"encoding/json"
"strings"

"github.com/vmware-tanzu/secrets-manager/core/constants/symbol"
entity "github.com/vmware-tanzu/secrets-manager/core/entity/v1/data"
"github.com/vmware-tanzu/secrets-manager/core/env"
log "github.com/vmware-tanzu/secrets-manager/core/log/std"
"regexp"
)

// WorkloadIDAndParts extracts the workload identifier and its constituent parts
Expand All @@ -34,11 +32,18 @@ import (
// prefix. The second return value is a slice of strings representing all
// parts of the SPIFFE ID after the prefix removal.
func WorkloadIDAndParts(spiffeid string) (string, []string) {
tmp := strings.Replace(spiffeid, env.SpiffeIdPrefixForWorkload(), "", 1)
parts := strings.Split(tmp, symbol.PathSeparator)
if len(parts) > 0 {
return parts[0], parts
re := env.NameRegExpForWorkload()
if re == "" {
return "", nil
}
wre := regexp.MustCompile(env.NameRegExpForWorkload())

match := wre.FindStringSubmatch(spiffeid)

if len(match) > 1 {
return match[1], match
}

return "", nil
}

Expand Down
2 changes: 1 addition & 1 deletion app/safe/internal/server/route/list/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
// - r: A pointer to an http.Request representing the received HTTP request.
// - spiffeid: spiffe id of the caller.
func Masked(
cid string, w http.ResponseWriter, r *http.Request,
cid string, r *http.Request, w http.ResponseWriter,
) {
doList(cid, w, r, false)
}
Expand Down
Empty file.
8 changes: 5 additions & 3 deletions core/constants/env/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ const VSecMSidecarSuccessThreshold VarName = "VSECM_SIDECAR_SUCCESS_THRESHOLD"
const VSecMSpiffeIdPrefixSafe VarName = "VSECM_SPIFFEID_PREFIX_SAFE"
const VSecMSpiffeIdPrefixSentinel VarName = "VSECM_SPIFFEID_PREFIX_SENTINEL"
const VSecMSpiffeIdPrefixWorkload VarName = "VSECM_SPIFFEID_PREFIX_WORKLOAD"
const VSecMWorkloadNameRegExp VarName = "VSECM_WORKLOAD_NAME_REGEXP"

type VarValue string

Expand Down Expand Up @@ -107,9 +108,10 @@ const VSecMSidecarMaxPollIntervalDefault VarValue = "300000"
const VSecMSidecarPollIntervalDefault VarValue = "20000"
const VSecMSidecarSecretsPathDefault VarValue = "/opt/vsecm/secrets.json"
const VSecMSidecarSuccessThresholdDefault VarValue = "3"
const VSecMSpiffeIdPrefixSafeDefault VarValue = "spiffe://vsecm.com/workload/vsecm-safe/ns/vsecm-system/sa/vsecm-safe/n/"
const VSecMSpiffeIdPrefixSentinelDefault VarValue = "spiffe://vsecm.com/workload/vsecm-sentinel/ns/vsecm-system/sa/vsecm-sentinel/n/"
const VSecMSpiffeIdPrefixWorkloadDefault VarValue = "spiffe://vsecm.com/workload/"
const VSecMSpiffeIdPrefixSafeDefault VarValue = "^spiffe://vsecm.com/workload/vsecm-safe/ns/vsecm-system/sa/vsecm-safe/n/[^/]+$"
const VSecMSpiffeIdPrefixSentinelDefault VarValue = "^spiffe://vsecm.com/workload/vsecm-sentinel/ns/vsecm-system/sa/vsecm-sentinel/n/[^/]+$"
const VSecMSpiffeIdPrefixWorkloadDefault VarValue = "^spiffe://vsecm.com/workload/[^/]+/ns/[^/]+/sa/[^/]+/n/[^/]+$"
const VSecMNameRegExpForWorkloadDefault VarValue = "^spiffe://vsecm.com/workload/([^/]+)/ns/[^/]+/sa/[^/]+/n/[^/]+$"

type Namespace string

Expand Down
13 changes: 13 additions & 0 deletions core/env/spiffeid.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,16 @@ func SpiffeIdPrefixForWorkload() string {
}
return p
}

// NameRegExpForWorkload returns the regular expression pattern for extracting
// the workload name from the SPIFFE ID.
// The prefix is obtained from the environment variable
// VSECM_NAME_REGEXP_FOR_WORKLOAD.
// If the variable is not set, the default pattern is used.
func NameRegExpForWorkload() string {
p := env.Value(env.VSecMWorkloadNameRegExp)
if p == "" {
p = string(env.VSecMNameRegExpForWorkloadDefault)
}
return p
}
11 changes: 6 additions & 5 deletions core/env/spiffeid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
package env

import (
"github.com/vmware-tanzu/secrets-manager/core/constants/env"
"os"
"testing"
)
Expand All @@ -24,7 +25,7 @@ func TestSentinelSpiffeIdPrefix(t *testing.T) {
}{
{
name: "default_sentinel_spiffeid_prefix",
want: "spiffe://vsecm.com/workload/vsecm-sentinel/ns/vsecm-system/sa/vsecm-sentinel/n/",
want: string(env.VSecMSpiffeIdPrefixSentinelDefault),
},
{
name: "sentinel_spiffeid_prefix_from_env",
Expand Down Expand Up @@ -67,7 +68,7 @@ func TestSafeSpiffeIdPrefix(t *testing.T) {
}{
{
name: "default_safe_spiffeid_prefix",
want: "spiffe://vsecm.com/workload/vsecm-safe/ns/vsecm-system/sa/vsecm-safe/n/",
want: string(env.VSecMSpiffeIdPrefixSafeDefault),
},
{
name: "safe_spiffeid_prefix_from_env",
Expand Down Expand Up @@ -110,17 +111,17 @@ func TestWorkloadSpiffeIdPrefix(t *testing.T) {
}{
{
name: "default_safe_spiffeid_prefix",
want: "spiffe://vsecm.com/workload/",
want: string(env.VSecMSpiffeIdPrefixWorkloadDefault),
},
{
name: "safe_spiffeid_prefix_from_env",
setup: func() error {
return os.Setenv("VSECM_SPIFFEID_PREFIX_WORKLOAD", "spiffe://vsecm.com/workload/test/")
return os.Setenv("VSECM_SPIFFEID_PREFIX_WORKLOAD", "spiffe://vsecm.com/workload/test/ns/test/sa/test/n/test")
},
cleanup: func() error {
return os.Unsetenv("VSECM_SPIFFEID_PREFIX_WORKLOAD")
},
want: "spiffe://vsecm.com/workload/test/",
want: "spiffe://vsecm.com/workload/test/ns/test/sa/test/n/test",
},
}
for _, tt := range tests {
Expand Down
22 changes: 21 additions & 1 deletion core/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ import (
"github.com/vmware-tanzu/secrets-manager/core/env"
)

const spiffeRegexPrefixStart = "^"
// Any SPIFFE ID regular expression matcher shall start with the
// `^spiffe://$trustDomain` prefix for extra security.
//
// This variable shall be treated as constant and should not be modified.
var spiffeRegexPrefixStart = "^spiffe://" + env.SpiffeTrustDomain() + "/"

// IsSentinel checks if a given SPIFFE ID belongs to VSecM Sentinel.
//
Expand All @@ -43,13 +47,18 @@ const spiffeRegexPrefixStart = "^"
//
// bool: `true` if the SPIFFE ID belongs to VSecM Sentinel, `false` otherwise.
func IsSentinel(spiffeid string) bool {
if !IsWorkload(spiffeid) {
return false
}

prefix := env.SpiffeIdPrefixForSentinel()

if strings.HasPrefix(prefix, spiffeRegexPrefixStart) {
re, err := regexp.Compile(prefix)
if err != nil {
return false
}

return re.MatchString(spiffeid)
}

Expand Down Expand Up @@ -83,6 +92,10 @@ func IsSentinel(spiffeid string) bool {
//
// bool: `true` if the SPIFFE ID belongs to VSecM Safe, `false` otherwise.
func IsSafe(spiffeid string) bool {
if !IsWorkload(spiffeid) {
return false
}

prefix := env.SpiffeIdPrefixForSafe()

if strings.HasPrefix(prefix, spiffeRegexPrefixStart) {
Expand Down Expand Up @@ -130,6 +143,7 @@ func IsWorkload(spiffeid string) bool {
if err != nil {
return false
}

return re.MatchString(spiffeid)
}

Expand All @@ -139,6 +153,12 @@ func IsWorkload(spiffeid string) bool {
return false
}

wre := regexp.MustCompile(env.NameRegExpForWorkload())
match := wre.FindStringSubmatch(spiffeid)
if len(match) == 0 {
return false
}

return strings.HasPrefix(spiffeid, prefix)
}

Expand Down
2 changes: 1 addition & 1 deletion core/validation/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func TestIsWorkload(t *testing.T) {
{
name: "has_prefix",
args: args{
spiffeid: "spiffe://vsecm.com/workload/test",
spiffeid: "spiffe://vsecm.com/workload/test/ns/test/sa/test/n/test",
},
want: true,
},
Expand Down
14 changes: 14 additions & 0 deletions hack/create-custom-manifest.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bash

# /*
# | Protect your secrets, protect your sensitive data.
# : Explore VMware Secrets Manager docs at https://vsecm.com/
# </
# <>/ keep your secrets... secret
# >/
# <>/' Copyright 2023-present VMware Secrets Manager contributors.
# >/' SPDX-License-Identifier: BSD-2-Clause
# */

cp ./helm-charts/0.25.4/values-custom.yaml ./helm-charts/0.25.4/values.yaml
make k8s-manifests-update VERSION=0.25.4
2 changes: 1 addition & 1 deletion hack/minikube-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ minikube start \
--extra-config=apiserver.service-account-signing-key-file=/var/lib/minikube/certs/sa.key \
--extra-config=apiserver.service-account-key-file=/var/lib/minikube/certs/sa.pub \
--extra-config=apiserver.service-account-issuer=api \
--extra-config=apiserver.api-audiences=api,spire-server \
--extra-config=apiserver.api-audiences=api,spire-server-custom \
--extra-config=apiserver.authorization-mode=Node,RBAC \
--memory="$MEMORY" \
--cpus="$CPU" \
Expand Down
6 changes: 3 additions & 3 deletions hack/print-spire-bundle.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
# >/' SPDX-License-Identifier: BSD-2-Clause
# */

SPIRE_SERVER=$(kubectl get po -n spire-server \
| grep "spire-server-" | awk '{print $1}')
SPIRE_SERVER=$(kubectl get po -n spire-server-custom \
| grep "spire-server-custom-" | awk '{print $1}')
export SPIRE_SERVER=SPIRE_SERVER

kubectl exec -n spire-system $SPIRE_SERVER -- \
kubectl exec -n spire-system-custom $SPIRE_SERVER -- \
/opt/spire/bin/spire-server bundle show
56 changes: 16 additions & 40 deletions helm-charts/0.25.4/charts/keystone/templates/Deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ spec:
{{- $workloadInitSpiffeIdPrefixSet := false }}
{{- $vsecmInitNamespaceSet := false }}
{{- $spireInitNamespaceSet := false }}
{{- $spiffeTrustDomainSet := false }}
{{- $workloadNameRegExpSet := false }}
{{- range .Values.initEnvironments }}
{{- if eq .name "VSECM_SAFE_ENDPOINT_URL" }}
{{- $safeInitEndpointUrlSet = true }}
Expand All @@ -81,6 +83,12 @@ spec:
{{ if eq .name "VSECM_NAMESPACE_SPIRE" }}
{{- $spireInitNamespaceSet = true }}
{{- end }}
{{ if eq .name "SPIFFE_TRUST_DOMAIN" }}
{{- $spiffeTrustDomainSet = true }}
{{- end }}
{{- if eq .name "VSECM_WORKLOAD_NAME_REGEXP" }}
{{- $workloadNameRegExpSet = true }}
{{- end }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
Expand All @@ -104,6 +112,14 @@ spec:
- name: VSECM_NAMESPACE_SPIRE
value: {{ .Values.global.spire.namespace | quote }}
{{- end }}
{{- if not $spiffeTrustDomainSet }}
- name: SPIFFE_TRUST_DOMAIN
value: {{ .Values.global.spire.trustDomain | quote }}
{{- end }}
{{- if not $workloadNameRegExpSet }}
- name: VSECM_WORKLOAD_NAME_REGEXP
value: {{ .Values.global.vsecm.workloadNameRegExp | quote }}
{{- end }}
containers:
- name: main
image: "{{ .Values.global.registry }}/{{- include "keystone.repository" .}}:{{ .Values.global.images.keystone.tag }}"
Expand All @@ -123,50 +139,10 @@ spec:
# Sentinel will assume the default values outlined in the given link above.
#
env:
{{- $safeEndpointUrlSet := false }}
{{- $safeSpiffeIdPrefixSet := false }}
{{- $workloadSpiffeIdPrefixSet := false }}
{{- $vsecmNamespaceSet := false }}
{{- $spireNamespaceSet := false }}
{{- range .Values.environments }}
{{- if eq .name "VSECM_SAFE_ENDPOINT_URL" }}
{{- $safeEndpointUrlSet = true }}
{{- end }}
{{- if eq .name "VSECM_SPIFFEID_PREFIX_SAFE" }}
{{- $safeSpiffeIdPrefixSet = true }}
{{- end }}
{{- if eq .name "VSECM_SPIFFEID_PREFIX_WORKLOAD" }}
{{- $workloadSpiffeIdPrefixSet = true }}
{{- end }}
{{ if eq .name "VSECM_NAMESPACE_SYSTEM" }}
{{- $vsecmNamespaceSet = true }}
{{- end }}
{{ if eq .name "VSECM_NAMESPACE_SPIRE" }}
{{- $spireNamespaceSet = true }}
{{- end }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
{{- if not $safeEndpointUrlSet }}
- name: VSECM_SAFE_ENDPOINT_URL
value: {{ .Values.global.vsecm.safeEndpointUrl | quote }}
{{- end }}
{{- if not $safeSpiffeIdPrefixSet }}
- name: VSECM_SPIFFEID_PREFIX_SAFE
value: {{ .Values.global.vsecm.safeSpiffeIdPrefix | quote }}
{{- end }}
{{- if not $workloadSpiffeIdPrefixSet }}
- name: VSECM_SPIFFEID_PREFIX_WORKLOAD
value: {{ .Values.global.vsecm.workloadSpiffeIdPrefix | quote }}
{{- end }}
{{- if not $vsecmNamespaceSet }}
- name: VSECM_NAMESPACE_SYSTEM
value: {{ .Values.global.vsecm.namespace | quote }}
{{- end }}
{{- if not $spireNamespaceSet }}
- name: VSECM_NAMESPACE_SPIRE
value: {{ .Values.global.spire.namespace | quote }}
{{- end }}
resources:
requests:
memory: {{ .Values.resources.requests.memory }}
Expand Down
Loading

0 comments on commit 2ac30f9

Please sign in to comment.