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
8 changes: 8 additions & 0 deletions api/v1alpha1/identitytokenrole_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,14 @@ func (d *IdentityTokenRole) IsInitialized() bool {
}

func (d *IdentityTokenRole) PrepareInternalValues(context context.Context, object client.Object) error {
if d.Spec.Template == "" {
return nil
}
resolved, err := vaultutils.ResolveAuthAccessors(context, d.Spec.Template)
if err != nil {
return err
}
d.Spec.Template = resolved
return nil
}

Expand Down
36 changes: 6 additions & 30 deletions api/v1alpha1/policy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,8 @@ package v1alpha1

import (
"context"
"errors"
"reflect"
"regexp"
"strings"

vault "github.com/hashicorp/vault/api"
vaultutils "github.com/redhat-cop/vault-config-operator/api/v1alpha1/utils"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -76,35 +72,15 @@ func (d *Policy) IsDeletable() bool {
}

func (d *Policy) PrepareInternalValues(context context.Context, object client.Object) error {
// Fast path escape if no "${..}" placeholder is detected
match, err := regexp.MatchString("\\${[^}]+}", d.Spec.Policy)
if err != nil || !match {
return nil
}

log := log.FromContext(context)

// Retrieves the list of auth engines to get their accessors
// Kinda duplicates logic found in VaultEngineObject.retrieveAccessor
vaultClient := context.Value("vaultClient").(*vault.Client)
secret, err := vaultClient.Logical().Read("sys/auth")
resolved, err := vaultutils.ResolveAuthAccessors(context, d.Spec.Policy)
if err != nil {
// Log but ignore the error: do not resolve placeholders
log.Error(err, "could not resolve auth engine accessor(s) in policy rule - unable to retrieve auth engines at", "path", "sys/auth")
return nil
return err
}
if secret == nil {
return errors.New("could not resolve auth engine accessor(s) in policy rule - listing auth engines at sys/auth unexpectedly returned null")
if resolved != d.Spec.Policy {
d.Spec.Policy = resolved
log := log.FromContext(context)
log.V(1).Info("Auth engine accessor(s) resolved", "policy", d.Spec.Policy)
}

for key, data := range secret.Data {
authenginepath := strings.Trim(key, "/")
placeholder := "${auth/" + authenginepath + "/@accessor}"
accessor := data.(map[string]interface{})["accessor"].(string)
d.Spec.Policy = strings.ReplaceAll(d.Spec.Policy, placeholder, accessor)
}

log.V(1).Info("Auth engine accessor(s) resolved", "policy", d.Spec.Policy)
return nil
}

Expand Down
33 changes: 33 additions & 0 deletions api/v1alpha1/utils/commons.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"errors"
"fmt"
"os"
"regexp"
"strings"
"sync"

Expand Down Expand Up @@ -433,6 +434,38 @@ func GetFinalizer(instance client.Object) string {
return "controller-" + strings.ToLower(instance.GetObjectKind().GroupVersionKind().Kind)
}

// ResolveAuthAccessors replaces all occurrences of ${auth/<auth engine path>/@accessor} in the given
// input string with the actual accessor value from Vault. It reads all auth engines from sys/auth
// and performs the replacement for each one. If no placeholders are found, the input is returned unchanged.
// The Vault role used for authentication must have read and list access to sys/auth for this to work.
func ResolveAuthAccessors(context context.Context, input string) (string, error) {
match, err := regexp.MatchString("\\${[^}]+}", input)
if err != nil || !match {
return input, nil
}

log := log.FromContext(context)

vaultClient := context.Value("vaultClient").(*vault.Client)
secret, err := vaultClient.Logical().Read("sys/auth")
if err != nil {
log.Error(err, "could not resolve auth engine accessor(s) - unable to retrieve auth engines at", "path", "sys/auth")
return input, nil
}
if secret == nil {
return input, errors.New("could not resolve auth engine accessor(s) - listing auth engines at sys/auth unexpectedly returned null")
}

for key, data := range secret.Data {
authenginepath := strings.Trim(key, "/")
placeholder := "${auth/" + authenginepath + "/@accessor}"
accessor := data.(map[string]interface{})["accessor"].(string)
input = strings.ReplaceAll(input, placeholder, accessor)
}

return input, nil
}

// +kubebuilder:object:generate=true
type TargetNamespaceConfig struct {
// TargetNamespaceSelector is a selector of namespaces from which service accounts will receove this role. Either TargetNamespaceSelector or TargetNamespaces can be specified
Expand Down
27 changes: 26 additions & 1 deletion docs/identities.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,4 +226,29 @@ The following fields are available:
- `key` - (required) A configured named key; the key must already exist.
- `template` - (optional) The template string to use for generating tokens. May be in string-ified JSON or base64 format.
- `clientID` - (optional) Client ID. A random ID will be generated if left unset.
- `ttl` - (optional, default: `"24h"`) TTL of the tokens generated against the role.
- `ttl` - (optional, default: `"24h"`) TTL of the tokens generated against the role.

### Auth engine accessor resolution in templates

The `template` field supports the same `${auth/<auth engine path>/@accessor}` placeholder syntax as [Policy](../docs/policy-management.md#policy). The operator will automatically replace these placeholders with the accessor of the auth engine mounted at the given path before writing the role to Vault.

For example:

```yaml
apiVersion: redhatcop.redhat.io/v1alpha1
kind: IdentityTokenRole
metadata:
name: identitytokenrole-with-accessor
spec:
authentication:
path: kubernetes
role: policy-admin
key: identitytokenkey-sample
template: |
{
"namespace": {{identity.entity.aliases.${auth/kubernetes/@accessor}.metadata.service_account_namespace}}
}
ttl: "24h"
```

Note: the Vault role used for authentication (specified in the `authentication` section) must have `read` and `list` access to Vault's `sys/auth` API endpoint for this automated resolution to work. Any unresolved placeholder is left as-is in the configured template.
Loading