Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support vault cacert bytes env #507

Merged
merged 13 commits into from
Oct 24, 2023
Merged
14 changes: 12 additions & 2 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ jobs:
- run: pip install yq
shell: bash

# Checkout this repo.
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
with:
path: "vault-k8s"

# Checkout vault-helm for acceptance test code.
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
with:
Expand All @@ -75,7 +80,12 @@ jobs:
docker image load --input vault-k8s-image.docker.tar
kind load docker-image hashicorp/vault-k8s:0.0.0-dev

- name: Makefile tests
working-directory: vault-k8s
run: make deploy exercise teardown

- name: bats tests
working-directory: vault-helm
run: |
yq --in-place --yaml-roundtrip '.injector.image.tag |= "0.0.0-dev"' ./vault-helm/values.yaml
bats ./vault-helm/test/acceptance -t --filter injector
yq --in-place --yaml-roundtrip '.injector.image.tag |= "0.0.0-dev"' ./values.yaml
bats ./test/acceptance -t --filter injector
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@

# Output directory for binaries built in CircleCI
/pkg
/dist/
/dist/
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ Changes:
* `k8s.io/client-go` v0.27.4 => v0.28.2
* `github.com/hashicorp/vault/sdk` v0.9.2 => v0.10.2

Improvements:
* Injector can set CA certificate for injected pods via `AGENT_INJECT_VAULT_CACERT_BYTES` env var or `-vault-cacert-bytes` flag [GH-507](https://github.com/hashicorp/vault-k8s/pull/507)

## 1.3.0 (August 16, 2023)

Improvements:
Expand Down
22 changes: 12 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ PKG=github.com/hashicorp/vault-k8s/version
LDFLAGS?="-X '$(PKG).Version=v$(VERSION)'"
TESTARGS ?= '-test.v'

HELM_CHART_VERSION ?= 0.25.0
VAULT_HELM_CHART_VERSION ?= 0.25.0
VAULT_HELM_FLAGS?=--repo https://helm.releases.hashicorp.com --version=$(VAULT_HELM_CHART_VERSION) \
--wait --timeout=5m \
--values=test/vault/dev.values.yaml \
--set 'injector.image.tag=$(VERSION)'

.PHONY: all test build image clean version deploy exercise teardown
all: build
Expand All @@ -33,16 +37,14 @@ image: build

# Deploys Vault dev server and a locally built Agent Injector.
# Run multiple times to deploy new builds of the injector.
deploy: image
deploy:
kind load docker-image hashicorp/vault-k8s:$(VERSION)
helm upgrade --install vault vault --repo https://helm.releases.hashicorp.com --version=$(HELM_CHART_VERSION) \
--wait --timeout=5m \
--set 'server.dev.enabled=true' \
--set 'server.logLevel=debug' \
--set 'injector.image.tag=$(VERSION)' \
--set 'injector.image.pullPolicy=Never' \
--set 'injector.affinity=null' \
--set 'injector.annotations.deployed=unix-$(shell date +%s)'
helm upgrade --install vault vault $(VAULT_HELM_FLAGS) \
--set "injector.enabled=false"
kubectl delete pod -l "app.kubernetes.io/instance=vault"
kubectl wait --for=condition=Ready --timeout=5m pod -l "app.kubernetes.io/instance=vault"
helm upgrade --install vault vault $(VAULT_HELM_FLAGS) \
--set "injector.extraEnvironmentVars.AGENT_INJECT_VAULT_CACERT_BYTES=$$(kubectl exec vault-0 -- cat /tmp/vault-ca.pem | base64)"

# Populates the Vault dev server with a secret, configures kubernetes auth, and
# deploys an nginx pod with annotations to have the secret injected.
Expand Down
8 changes: 7 additions & 1 deletion agent-inject/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,15 @@ type Vault struct {
AuthConfig map[string]interface{}

// CACert is the name of the Certificate Authority certificate
// to use when validating Vault's server certificates.
// to use when validating Vault's server certificates. It takes
// precedence over CACertBytes.
CACert string

// CACertBytes is the contents of the CA certificate to trust
// for TLS with Vault as a PEM-encoded certificate or bundle.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this expected to be b64 encoded? Should we specify one way or another?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be PEM-encoded or base64. I added some comments in d50e787 to document the optional base64 encoding.

// Can also be base64 encoded PEM contents.
CACertBytes string

// CAKey is the name of the Certificate Authority key
// to use when validating Vault's server certificates.
CAKey string
Expand Down
16 changes: 16 additions & 0 deletions agent-inject/agent/container_env.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,13 @@ func (a *Agent) ContainerEnvVars(init bool) ([]corev1.EnvVar, error) {
}
}

if a.Vault.CACertBytes != "" {
envs = append(envs, corev1.EnvVar{
Name: "VAULT_CACERT_BYTES",
Value: decodeIfBase64(a.Vault.CACertBytes),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess going back to my previous comment, is there any reason not to make b64 encoding a requirement? The formatting of certs with all their carriage returns is to me frustrating at best trying to get into string values.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the benefit of making b64 required. Supporting the raw PEM is nice because it's how the environment variable is eventually consumed anyway, and supporting b64 is nice because as you say it can be frustrating when there are multiple levels of interpretation in the automation that gets the value into place.

})
}

// Add IRSA AWS Env variables for vault containers
if a.Vault.AuthType == "aws" {
envMap := a.getAwsEnvsFromContainer(a.Pod)
Expand All @@ -160,3 +167,12 @@ func (a *Agent) ContainerEnvVars(init bool) ([]corev1.EnvVar, error) {

return envs, nil
}

func decodeIfBase64(s string) string {
decoded, err := base64.StdEncoding.DecodeString(s)
if err == nil {
return string(decoded)
}

return s
}
2 changes: 1 addition & 1 deletion agent-inject/agent/container_init_sidecar.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"encoding/json"
"fmt"

"github.com/evanphx/json-patch"
jsonpatch "github.com/evanphx/json-patch"
corev1 "k8s.io/api/core/v1"
)

Expand Down
2 changes: 2 additions & 0 deletions agent-inject/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type Handler struct {
// If this is false, injection is default.
RequireAnnotation bool
VaultAddress string
VaultCACertBytes string
VaultAuthType string
VaultAuthPath string
VaultNamespace string
Expand Down Expand Up @@ -226,6 +227,7 @@ func (h *Handler) Mutate(req *admissionv1.AdmissionRequest) *admissionv1.Admissi
err := fmt.Errorf("error creating new agent sidecar: %s", err)
return admissionError(req.UID, err)
}
agentSidecar.Vault.CACertBytes = h.VaultCACertBytes

h.Log.Debug("validating agent configuration..")
err = agentSidecar.Validate()
Expand Down
3 changes: 2 additions & 1 deletion subcommand/injector/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type Command struct {
flagAutoName string // MutatingWebhookConfiguration for updating
flagAutoHosts string // SANs for the auto-generated TLS cert.
flagVaultService string // Name of the Vault service
flagVaultCACertBytes string // CA Cert to trust for TLS with Vault.
flagProxyAddress string // HTTP proxy address used to talk to the Vault service
flagVaultImage string // Name of the Vault Image to use
flagVaultAuthType string // Type of Vault Auth Method to use
Expand Down Expand Up @@ -87,7 +88,6 @@ type Command struct {
cert atomic.Value
}

// TODO Add flag for Vault TLS
func (c *Command) Run(args []string) int {
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
Expand Down Expand Up @@ -194,6 +194,7 @@ func (c *Command) Run(args []string) int {
// Build the HTTP handler and server
injector := agentInject.Handler{
VaultAddress: c.flagVaultService,
VaultCACertBytes: c.flagVaultCACertBytes,
VaultAuthType: c.flagVaultAuthType,
VaultAuthPath: c.flagVaultAuthPath,
VaultNamespace: c.flagVaultNamespace,
Expand Down
12 changes: 12 additions & 0 deletions subcommand/injector/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ type Specification struct {
// VaultAddr is the AGENT_INJECT_VAULT_ADDR environment variable.
VaultAddr string `split_words:"true"`

// VaultCACertBytes is the AGENT_INJECT_VAULT_CACERT_BYTES environment variable.
// Specifies the CA cert to trust for TLS with Vault as a PEM-encoded
// certificate or bundle. The multi-line PEM contents may optionally be base64
// encoded to avoid line breaks.
VaultCACertBytes string `envconfig:"AGENT_INJECT_VAULT_CACERT_BYTES"`

// ProxyAddr is the AGENT_INJECT_PROXY_ADDR environment variable.
ProxyAddr string `split_words:"true"`

Expand Down Expand Up @@ -159,6 +165,9 @@ func (c *Command) init() {
fmt.Sprintf("Docker image for Vault. Defaults to %q.", agent.DefaultVaultImage))
c.flagSet.StringVar(&c.flagVaultService, "vault-address", "",
"Address of the Vault server.")
c.flagSet.StringVar(&c.flagVaultCACertBytes, "vault-cacert-bytes", "",
"CA certificate to trust for TLS with Vault, specified as a PEM-encoded certificate or bundle. "+
"The multi-line PEM contents may optionally be base64 encoded to avoid line breaks.")
c.flagSet.StringVar(&c.flagProxyAddress, "proxy-address", "",
"HTTP proxy address used to talk to the Vault service.")
c.flagSet.StringVar(&c.flagVaultAuthType, "vault-auth-type", agent.DefaultVaultAuthType,
Expand Down Expand Up @@ -295,6 +304,9 @@ func (c *Command) parseEnvs() error {
if envs.VaultAddr != "" {
c.flagVaultService = envs.VaultAddr
}
if envs.VaultCACertBytes != "" {
c.flagVaultCACertBytes = envs.VaultCACertBytes
}

if envs.ProxyAddr != "" {
c.flagProxyAddress = envs.ProxyAddr
Expand Down
1 change: 1 addition & 0 deletions subcommand/injector/flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ func TestCommandEnvs(t *testing.T) {
}{
{env: "AGENT_INJECT_LISTEN", value: ":8080", cmdPtr: &cmd.flagListen},
{env: "AGENT_INJECT_VAULT_ADDR", value: "http://vault:8200", cmdPtr: &cmd.flagVaultService},
{env: "AGENT_INJECT_VAULT_CACERT_BYTES", value: "foo", cmdPtr: &cmd.flagVaultCACertBytes},
{env: "AGENT_INJECT_PROXY_ADDR", value: "http://proxy:3128", cmdPtr: &cmd.flagProxyAddress},
{env: "AGENT_INJECT_VAULT_AUTH_PATH", value: "auth-path-test", cmdPtr: &cmd.flagVaultAuthPath},
{env: "AGENT_INJECT_VAULT_IMAGE", value: "hashicorp/vault:1.14.1", cmdPtr: &cmd.flagVaultImage},
Expand Down
24 changes: 24 additions & 0 deletions test/vault/dev.values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
global:
tlsDisable: false
injector:
image:
pullPolicy: Never
affinity: null
agentImage:
tag: 1.15.0
server:
image:
tag: 1.15.0
dev:
enabled: true
logLevel: debug
# >- to convert to a single line with no line breaks.
extraArgs: >-
-dev-tls
-dev-tls-cert-dir=/tmp
-dev-tls-san=vault.default.svc.cluster.local
-dev-tls-san=vault.default.svc
-dev-tls-san=vault.default
-dev-tls-san=vault
extraEnvironmentVars:
VAULT_CACERT: /tmp/vault-ca.pem