Skip to content

Commit

Permalink
vault: support authentication in different namespaces
Browse files Browse the repository at this point in the history
This commit adds support for authenticating within a different
namespace compared to the secret engine namespace.

For example, KES should use the AppRole authentication engine
within the root namespace to authenticate but then use the
K/V and transit engine within a user-specific namespace, like
'foo'. Similarly, KES may authenticate in the namespace 'foo'
but use the secret engines in namespace 'bar' or 'foo/bar'.
With this change, this use case is now supported.

The special namespace string '/' is treated as alias for the root
namespace and causes KES to not send any namespace header for
authentication. If the namespace header for root should be set
explicitly, use the namespace 'root/'.

Signed-off-by: Andreas Auernhammer <[email protected]>
  • Loading branch information
aead committed Jan 11, 2024
1 parent 9c7a5dc commit 4c024e4
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 34 deletions.
20 changes: 18 additions & 2 deletions internal/keystore/vault/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,15 @@ func (c *client) CheckStatus(ctx context.Context, delay time.Duration) {
// To renew the auth. token see: client.RenewToken(...).
func (c *client) AuthenticateWithAppRole(login *AppRole) authFunc {
return func() (*vaultapi.Secret, error) {
secret, err := c.Logical().Write(path.Join("auth", login.Engine, "login"), map[string]interface{}{
client := c.Client
switch {
case login.Namespace == "/": // Treat '/' as the root namespace
client = client.WithNamespace("") // Clear namespace
case login.Namespace != "":
client = client.WithNamespace(login.Namespace)
}

secret, err := client.Logical().Write(path.Join("auth", login.Engine, "login"), map[string]interface{}{
"role_id": login.ID,
"secret_id": login.Secret,
})
Expand All @@ -88,7 +96,15 @@ func (c *client) AuthenticateWithAppRole(login *AppRole) authFunc {

func (c *client) AuthenticateWithK8S(login *Kubernetes) authFunc {
return func() (*vaultapi.Secret, error) {
secret, err := c.Logical().Write(path.Join("auth", login.Engine, "login"), map[string]interface{}{
client := c.Client
switch {
case login.Namespace == "/": // Treat '/' as the root namespace
client = client.WithNamespace("") // Clear namespace
case login.Namespace != "":
client = client.WithNamespace(login.Namespace)
}

secret, err := client.Logical().Write(path.Join("auth", login.Engine, "login"), map[string]interface{}{
"role": login.Role,
"jwt": login.JWT,
})
Expand Down
40 changes: 34 additions & 6 deletions internal/keystore/vault/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,19 @@ type AppRole struct {
// mounted at arbitrary paths.
Engine string

// Namespace is the Vault namespace in which the AppRole
// authentication is performed. It can be used to authenticate
// in a different namespace compared to the secret engine
// namespace. For example, authenticate within the root
// namespace but use a team-specific namespace for the secret
// engine.
//
// If empty, the VaultKeyStore namespace is used, if set.
// A single "/" is treated as alias for the Vault root
// namespace such that no namespace header is sent as part
// of the request.
Namespace string

// ID is the AppRole authentication ID
ID string

Expand All @@ -62,9 +75,10 @@ func (a *AppRole) Clone() *AppRole {
return nil
}
return &AppRole{
Engine: a.Engine,
ID: a.ID,
Secret: a.Secret,
Engine: a.Engine,
Namespace: a.Namespace,
ID: a.ID,
Secret: a.Secret,
}
}

Expand All @@ -81,6 +95,19 @@ type Kubernetes struct {
// mounted at arbitrary paths.
Engine string

// Namespace is the Vault namespace in which the Kubernetes
// authentication is performed. It can be used to authenticate
// in a different namespace compared to the secret engine
// namespace. For example, authenticate within the root
// namespace but use a team-specific namespace for the secret
// engine.
//
// If empty, the VaultKeyStore namespace is used, if set.
// A single "/" is treated as alias for the Vault root
// namespace such that no namespace header is sent as part
// of the request.
Namespace string

// Role is the JWT role.
Role string

Expand All @@ -94,9 +121,10 @@ func (k *Kubernetes) Clone() *Kubernetes {
return nil
}
return &Kubernetes{
Engine: k.Engine,
Role: k.Role,
JWT: k.JWT,
Engine: k.Engine,
Namespace: k.Namespace,
Role: k.Role,
JWT: k.JWT,
}
}

Expand Down
28 changes: 16 additions & 12 deletions kesconf/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,17 @@ type ymlFile struct {
}

AppRole *struct {
Engine env[string] `yaml:"engine"`
ID env[string] `yaml:"id"`
Secret env[string] `yaml:"secret"`
Engine env[string] `yaml:"engine"`
Namespace env[string] `yaml:"namespace"`
ID env[string] `yaml:"id"`
Secret env[string] `yaml:"secret"`
} `yaml:"approle"`

Kubernetes *struct {
Engine env[string] `yaml:"engine"`
Role env[string] `yaml:"role"`
JWT env[string] `yaml:"jwt"` // Can be either a JWT or a path to a file containing a JWT
Engine env[string] `yaml:"engine"`
Namespace env[string] `yaml:"namespace"`
Role env[string] `yaml:"role"`
JWT env[string] `yaml:"jwt"` // Can be either a JWT or a path to a file containing a JWT
} `yaml:"kubernetes"`

TLS struct {
Expand Down Expand Up @@ -463,16 +465,18 @@ func ymlToKeyStore(y *ymlFile) (KeyStore, error) {
}
if y.KeyStore.Vault.AppRole != nil {
s.AppRole = &VaultAppRoleAuth{
Engine: y.KeyStore.Vault.AppRole.Engine.Value,
ID: y.KeyStore.Vault.AppRole.ID.Value,
Secret: y.KeyStore.Vault.AppRole.Secret.Value,
Engine: y.KeyStore.Vault.AppRole.Engine.Value,
Namespace: y.KeyStore.Vault.AppRole.Namespace.Value,
ID: y.KeyStore.Vault.AppRole.ID.Value,
Secret: y.KeyStore.Vault.AppRole.Secret.Value,
}
}
if y.KeyStore.Vault.Kubernetes != nil {
s.Kubernetes = &VaultKubernetesAuth{
Engine: y.KeyStore.Vault.Kubernetes.Engine.Value,
JWT: y.KeyStore.Vault.Kubernetes.JWT.Value,
Role: y.KeyStore.Vault.Kubernetes.Role.Value,
Engine: y.KeyStore.Vault.Kubernetes.Engine.Value,
Namespace: y.KeyStore.Vault.Kubernetes.Namespace.Value,
JWT: y.KeyStore.Vault.Kubernetes.JWT.Value,
Role: y.KeyStore.Vault.Kubernetes.Role.Value,
}
}
if y.KeyStore.Vault.Transit != nil {
Expand Down
40 changes: 34 additions & 6 deletions kesconf/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,19 @@ type VaultAppRoleAuth struct {
// If empty, defaults to "approle".
Engine string

// Namespace is the Vault namespace in which the AppRole
// authentication is performed. It can be used to authenticate
// in a different namespace compared to the secret engine
// namespace. For example, authenticate within the root
// namespace but use a team-specific namespace for the secret
// engine.
//
// If empty, the VaultKeyStore namespace is used, if set.
// A single "/" is treated as alias for the Vault root
// namespace such that no namespace header is sent as part
// of the request.
Namespace string

// AppRoleID is the AppRole access ID for authenticating
// to Hashicorp Vault via the AppRole method.
ID string
Expand All @@ -477,6 +490,19 @@ type VaultKubernetesAuth struct {
// If empty, defaults to "kubernetes".
Engine string

// Namespace is the Vault namespace in which the Kubernetes
// authentication is performed. It can be used to authenticate
// in a different namespace compared to the secret engine
// namespace. For example, authenticate within the root
// namespace but use a team-specific namespace for the secret
// engine.
//
// If empty, the VaultKeyStore namespace is used, if set.
// A single "/" is treated as alias for the Vault root
// namespace such that no namespace header is sent as part
// of the request.
Namespace string

// KubernetesRole is the login role for authenticating via the
// kubernetes authentication method.
Role string
Expand Down Expand Up @@ -519,16 +545,18 @@ func (s *VaultKeyStore) Connect(ctx context.Context) (kes.KeyStore, error) {
}
if s.AppRole != nil {
c.AppRole = &vault.AppRole{
Engine: s.AppRole.Engine,
ID: s.AppRole.ID,
Secret: s.AppRole.Secret,
Engine: s.AppRole.Engine,
Namespace: s.AppRole.Namespace,
ID: s.AppRole.ID,
Secret: s.AppRole.Secret,
}
}
if s.Kubernetes != nil {
c.K8S = &vault.Kubernetes{
Engine: s.Kubernetes.Engine,
Role: s.Kubernetes.Role,
JWT: s.Kubernetes.JWT,
Engine: s.Kubernetes.Engine,
Namespace: s.Kubernetes.Namespace,
Role: s.Kubernetes.Role,
JWT: s.Kubernetes.JWT,
}
}
if s.Transit != nil {
Expand Down
16 changes: 8 additions & 8 deletions server-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -249,15 +249,15 @@ keystore:
engine: "" # The path of the transit engine - e.g. "my-transit". If empty, defaults to: transit (Vault default)
key: "" # The key name that should be used to encrypt entries stored on the K/V engine.
approle: # AppRole credentials. See: https://www.vaultproject.io/docs/auth/approle.html
engine: "" # The path of the AppRole engine - e.g. authenticate. If empty, defaults to: approle. (Vault default)
id: "" # Your AppRole Role ID
secret: "" # Your AppRole Secret ID
retry: 15s # Duration until the server tries to re-authenticate after connection loss.
namespace: "" # Optional Vault namespace used just for authentication. A single "/" is an alias for the Vault root namespace.
engine: "" # The path of the AppRole engine - e.g. authenticate. If empty, defaults to: approle. (Vault default)
id: "" # Your AppRole Role ID
secret: "" # Your AppRole Secret ID
kubernetes: # Kubernetes credentials. See: https://www.vaultproject.io/docs/auth/kubernetes
engine: "" # The path of the Kubernetes engine e.g. authenticate. If empty, defaults to: kubernetes. (Vault default)
role: "" # The Kubernetes JWT role
jwt: "" # Either the JWT provided by K8S or a path to a K8S secret containing the JWT.
retry: 15s # Duration until the server tries to re-authenticate after connection loss.
namespace: "" # Optional Vault namespace used just for authentication. A single "/" is an alias for the Vault root namespace.
engine: "" # The path of the Kubernetes engine e.g. authenticate. If empty, defaults to: kubernetes. (Vault default)
role: "" # The Kubernetes JWT role
jwt: "" # Either the JWT provided by K8S or a path to a K8S secret containing the JWT.
tls: # The Vault client TLS configuration for mTLS authentication and certificate verification
key: "" # Path to the TLS client private key for mTLS authentication to Vault
cert: "" # Path to the TLS client certificate for mTLS authentication to Vault
Expand Down

0 comments on commit 4c024e4

Please sign in to comment.