Skip to content

Commit

Permalink
Add support for all Auth Methods via annotations (hashicorp#213)
Browse files Browse the repository at this point in the history
* Generalize Agent Auto Auth to allow all methods

No fail if Role not specified unless K8s auth

Add tests

Signed-off-by: Pierce Bartine <piercebartine@gmail.com>

* Fix merge conflict

* Tweak test conditions

* Default Auto-Auth type and validation
  • Loading branch information
pbar1 authored Feb 25, 2021
1 parent 2593259 commit ca930f5
Show file tree
Hide file tree
Showing 10 changed files with 176 additions and 38 deletions.
18 changes: 16 additions & 2 deletions agent-inject/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

const (
DefaultVaultImage = "vault:1.6.2"
DefaultVaultAuthType = "kubernetes"
DefaultVaultAuthPath = "auth/kubernetes"
DefaultAgentRunAsUser = 100
DefaultAgentRunAsGroup = 1000
Expand Down Expand Up @@ -163,9 +164,15 @@ type Vault struct {
// ProxyAddress is the proxy service address to use when talking to the Vault service.
ProxyAddress string

// AuthPath is the Mount Path of Vault Kubernetes Auth Method.
// AuthType is type of Vault Auth Method to use.
AuthType string

// AuthPath is the Mount Path of Vault Auth Method.
AuthPath string

// AuthConfig is the Auto Auth Method configuration.
AuthConfig map[string]interface{}

// CACert is the name of the Certificate Authority certificate
// to use when validating Vault's server certificates.
CACert string
Expand Down Expand Up @@ -248,6 +255,7 @@ func New(pod *corev1.Pod, patches []*jsonpatch.JsonPatchOperation) (*Agent, erro
Vault: Vault{
Address: pod.Annotations[AnnotationVaultService],
ProxyAddress: pod.Annotations[AnnotationProxyAddress],
AuthType: pod.Annotations[AnnotationVaultAuthType],
AuthPath: pod.Annotations[AnnotationVaultAuthPath],
CACert: pod.Annotations[AnnotationVaultCACert],
CAKey: pod.Annotations[AnnotationVaultCAKey],
Expand All @@ -266,6 +274,7 @@ func New(pod *corev1.Pod, patches []*jsonpatch.JsonPatchOperation) (*Agent, erro

var err error
agent.Secrets = agent.secrets()
agent.Vault.AuthConfig = agent.authConfig()
agent.Inject, err = agent.inject()
if err != nil {
return agent, err
Expand Down Expand Up @@ -508,7 +517,12 @@ func (a *Agent) Validate() error {
}

if a.ConfigMapName == "" {
if a.Vault.Role == "" {
if a.Vault.AuthType == "" {
return errors.New("no Vault Auth Type found")
}

if a.Vault.AuthType == DefaultVaultAuthType &&
a.Vault.Role == "" && a.Annotations[fmt.Sprintf("%s-role", AnnotationVaultAuthConfig)] == "" {
return errors.New("no Vault role found")
}

Expand Down
26 changes: 22 additions & 4 deletions agent-inject/agent/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ func TestValidate(t *testing.T) {
Role: "test",
Address: "https://foobar.com:8200",
AuthPath: "test",
AuthType: "kubernetes",
},
}, true,
},
Expand Down Expand Up @@ -129,8 +130,9 @@ func TestValidate(t *testing.T) {
ServiceAccountName: "foobar",
ImageName: "test",
Vault: Vault{
Role: "",
Address: "https://foobar.com:8200",
Role: "",
Address: "https://foobar.com:8200",
AuthType: "kubernetes",
},
}, false,
},
Expand All @@ -141,8 +143,9 @@ func TestValidate(t *testing.T) {
ServiceAccountName: "foobar",
ImageName: "test",
Vault: Vault{
Role: "test",
Address: "",
Role: "test",
Address: "",
AuthType: "kubernetes",
},
}, false,
},
Expand All @@ -156,6 +159,21 @@ func TestValidate(t *testing.T) {
Role: "test",
Address: "https://foobar.com:8200",
AuthPath: "",
AuthType: "kubernetes",
},
}, false,
},
{
Agent{
Namespace: "test",
ServiceAccountPath: "foobar",
ServiceAccountName: "foobar",
ImageName: "test",
Vault: Vault{
Role: "test",
Address: "https://foobar.com:8200",
AuthPath: "test",
AuthType: "",
},
}, false,
},
Expand Down
37 changes: 35 additions & 2 deletions agent-inject/agent/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,17 @@ const (
// method.
AnnotationVaultRole = "vault.hashicorp.com/role"

// AnnotationVaultAuthPath specifies the mount path to be used for the Kubernetes auto-auth
// method.
// AnnotationVaultAuthType specifies the auto-auth method type to be used.
AnnotationVaultAuthType = "vault.hashicorp.com/auth-type"

// AnnotationVaultAuthPath specifies the mount path to be used for the auto-auth method.
AnnotationVaultAuthPath = "vault.hashicorp.com/auth-path"

// AnnotationVaultAuthConfig specifies the Auto Auth Method configuration parameters.
// The name of the parameter is any unique string after "vault.hashicorp.com/auth-config-",
// such as "vault.hashicorp.com/auth-config-foobar".
AnnotationVaultAuthConfig = "vault.hashicorp.com/auth-config"

// AnnotationVaultSecretVolumePath specifies where the secrets are to be
// Mounted after fetching.
AnnotationVaultSecretVolumePath = "vault.hashicorp.com/secret-volume-path"
Expand Down Expand Up @@ -207,6 +214,7 @@ const (
type AgentConfig struct {
Image string
Address string
AuthType string
AuthPath string
Namespace string
RevokeOnShutdown bool
Expand Down Expand Up @@ -250,6 +258,13 @@ func Init(pod *corev1.Pod, cfg AgentConfig) error {
pod.ObjectMeta.Annotations[AnnotationVaultService] = cfg.Address
}

if _, ok := pod.ObjectMeta.Annotations[AnnotationVaultAuthType]; !ok {
if cfg.AuthType == "" {
cfg.AuthType = DefaultVaultAuthType
}
pod.ObjectMeta.Annotations[AnnotationVaultAuthType] = cfg.AuthType
}

if _, ok := pod.ObjectMeta.Annotations[AnnotationVaultAuthPath]; !ok {
pod.ObjectMeta.Annotations[AnnotationVaultAuthPath] = cfg.AuthPath
}
Expand Down Expand Up @@ -534,3 +549,21 @@ func (a *Agent) agentCacheEnable() (bool, error) {

return strconv.ParseBool(raw)
}

func (a *Agent) authConfig() map[string]interface{} {
authConfig := make(map[string]interface{})

prefix := fmt.Sprintf("%s-", AnnotationVaultAuthConfig)
for annotation, value := range a.Annotations {
if strings.HasPrefix(annotation, prefix) {
param := strings.TrimPrefix(annotation, prefix)
param = strings.ReplaceAll(param, "-", "_")
authConfig[param] = value
}
}
if a.Vault.Role != "" {
authConfig["role"] = a.Vault.Role
}

return authConfig
}
86 changes: 74 additions & 12 deletions agent-inject/agent/annotations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestInitCanSet(t *testing.T) {
pod := testPod(annotations)

agentConfig := AgentConfig{
"foobar-image", "http://foobar:8200", "test", "test", true, "100", "1000",
"foobar-image", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000",
DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, "http://proxy:3128",
}
err := Init(pod, agentConfig)
Expand Down Expand Up @@ -56,7 +56,7 @@ func TestInitDefaults(t *testing.T) {
pod := testPod(annotations)

agentConfig := AgentConfig{
"", "http://foobar:8200", "test", "test", true, "", "",
"", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "", "",
DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, "",
}
err := Init(pod, agentConfig)
Expand Down Expand Up @@ -90,7 +90,7 @@ func TestInitError(t *testing.T) {
pod := testPod(annotations)

agentConfig := AgentConfig{
"image", "", "authPath", "namespace", true, "100", "1000",
"image", "", DefaultVaultAuthType, "authPath", "namespace", true, "100", "1000",
DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, "",
}
err := Init(pod, agentConfig)
Expand Down Expand Up @@ -154,7 +154,7 @@ func TestSecretAnnotationsWithPreserveCaseSensitivityFlagOff(t *testing.T) {
var patches []*jsonpatch.JsonPatchOperation

agentConfig := AgentConfig{
"", "http://foobar:8200", "test", "test", true, "100", "1000",
"", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000",
DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, "",
}
err := Init(pod, agentConfig)
Expand Down Expand Up @@ -205,7 +205,7 @@ func TestSecretAnnotationsWithPreserveCaseSensitivityFlagOn(t *testing.T) {
var patches []*jsonpatch.JsonPatchOperation

agentConfig := AgentConfig{
"", "http://foobar:8200", "test", "test", true, "100", "1000",
"", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000",
DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, "",
}
err := Init(pod, agentConfig)
Expand Down Expand Up @@ -292,7 +292,7 @@ func TestSecretLocationFileAnnotations(t *testing.T) {
var patches []*jsonpatch.JsonPatchOperation

agentConfig := AgentConfig{
"", "http://foobar:8200", "test", "test", true, "100", "1000",
"", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000",
DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, "",
}
err := Init(pod, agentConfig)
Expand Down Expand Up @@ -377,7 +377,7 @@ func TestSecretTemplateAnnotations(t *testing.T) {
var patches []*jsonpatch.JsonPatchOperation

agentConfig := AgentConfig{
"", "http://foobar:8200", "test", "test", true, "100", "1000",
"", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000",
DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, "",
}
err := Init(pod, agentConfig)
Expand Down Expand Up @@ -437,7 +437,7 @@ func TestTemplateShortcuts(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
pod := testPod(tt.annotations)
agentConfig := AgentConfig{
"", "http://foobar:8200", "test", "test", true, "100", "1000",
"", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000",
DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, "",
}
err := Init(pod, agentConfig)
Expand Down Expand Up @@ -497,7 +497,7 @@ func TestSecretCommandAnnotations(t *testing.T) {
for _, tt := range tests {
pod := testPod(tt.annotations)
agentConfig := AgentConfig{
"", "http://foobar:8200", "test", "test", true, "100", "1000",
"", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000",
DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, "",
}
err := Init(pod, agentConfig)
Expand Down Expand Up @@ -630,7 +630,7 @@ func TestCouldErrorAnnotations(t *testing.T) {
var patches []*jsonpatch.JsonPatchOperation

agentConfig := AgentConfig{
"", "http://foobar:8200", "test", "test", true, "100", "1000",
"", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000",
DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, "",
}
err := Init(pod, agentConfig)
Expand All @@ -651,7 +651,7 @@ func TestInitEmptyPod(t *testing.T) {
var pod *corev1.Pod

agentConfig := AgentConfig{
"foobar-image", "http://foobar:8200", "test", "test", true, "100", "1000",
"foobar-image", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000",
DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, "",
}
err := Init(pod, agentConfig)
Expand Down Expand Up @@ -680,7 +680,7 @@ func TestVaultNamespaceAnnotation(t *testing.T) {
var patches []*jsonpatch.JsonPatchOperation

agentConfig := AgentConfig{
"foobar-image", "http://foobar:8200", "test", "test", true, "100", "1000",
"foobar-image", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000",
DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, "",
}
err := Init(pod, agentConfig)
Expand Down Expand Up @@ -794,3 +794,65 @@ func Test_runAsSameID(t *testing.T) {
})
}
}

func TestAuthConfigAnnotations(t *testing.T) {
tests := []struct {
annotations map[string]string
expectedAuthConfig map[string]interface{}
}{
{
map[string]string{
"vault.hashicorp.com/role": "backwardscompat",
},
map[string]interface{}{
"role": "backwardscompat",
},
},
{
map[string]string{
"vault.hashicorp.com/role": "backwardscompat",
"vault.hashicorp.com/auth-config-role": "lowerprio",
},
map[string]interface{}{
"role": "backwardscompat",
},
},
{
map[string]string{
"vault.hashicorp.com/auth-config-name": "foo",
"vault.hashicorp.com/auth-config-ca-cert": "bar",
"vault.hashicorp.com/auth-config-client_cert": "baz",
"vault.hashicorp.com/auth-config-credential_poll_interval": "1",
"vault.hashicorp.com/auth-config-remove_secret_id_file_after_reading": "false",
},
map[string]interface{}{
"name": "foo",
"ca_cert": "bar", // param name dashes converted to underscores for ease
"client_cert": "baz",
"credential_poll_interval": "1", // string->int conversion left up to consuming app HCL parser
"remove_secret_id_file_after_reading": "false", // string->bool, same as above
},
},
}

for _, tt := range tests {
pod := testPod(tt.annotations)
var patches []*jsonpatch.JsonPatchOperation

agentConfig := AgentConfig{
"", "http://foobar:8200", DefaultVaultAuthType, "test", "test", true, "100", "1000",
DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, "",
}
err := Init(pod, agentConfig)
if err != nil {
t.Errorf("got error, shouldn't have: %s", err)
}

agent, err := New(pod, patches)
if err != nil {
t.Errorf("got error, shouldn't have: %s", err)
}

require.Equal(t, agent.Vault.AuthConfig, tt.expectedAuthConfig, "expected AuthConfig %v, got %v", tt.expectedAuthConfig, agent.Vault.AuthConfig)
}
}
6 changes: 2 additions & 4 deletions agent-inject/agent/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,10 @@ func (a *Agent) newConfig(init bool) ([]byte, error) {
},
AutoAuth: &AutoAuth{
Method: &Method{
Type: "kubernetes",
Type: a.Vault.AuthType,
Namespace: a.Vault.Namespace,
MountPath: a.Vault.AuthPath,
Config: map[string]interface{}{
"role": a.Vault.Role,
},
Config: a.Vault.AuthConfig,
},
Sinks: []*Sink{
{
Expand Down
Loading

0 comments on commit ca930f5

Please sign in to comment.