ソースを参照

feat(openbao): add `auth.appRole` auth method (#6497)

Co-authored-by: Gergely Bräutigam <gergely.brautigam@sap.com>
Signed-off-by: Philipp Stehle <philipp.stehle@secretz.io>
Philipp Stehle 7 時間 前
コミット
b5230a1478

+ 52 - 0
apis/externalsecrets/v1/secretstore_openbao_types.go

@@ -36,12 +36,14 @@ type OpenBaoProvider struct {
 	// PEM encoded CA bundle used to validate the OpenBao server certificate. If
 	// this and `caProvider` are not set the system root certificates are used
 	// to validate the TLS connection.
+	//
 	// +optional
 	CABundle []byte `json:"caBundle,omitempty"`
 
 	// The provider for the CA bundle to use to validate OpenBao server
 	// certificate. If this and `caBundle` are not set the system root
 	// certificates are used to validate the TLS connection.
+	//
 	// +optional
 	CAProvider *CAProvider `json:"caProvider,omitempty"`
 
@@ -52,11 +54,13 @@ type OpenBaoProvider struct {
 	// "secret". The v2 KV secret engine version specific "/data" path suffix
 	// for fetching secrets from OpenBao is optional and will be appended
 	// if not present in specified path.
+	//
 	// +optional
 	Path *string `json:"path,omitempty"`
 
 	// Version is the OpenBao KV secret engine version. This can be either "v1" or
 	// "v2". Version defaults to "v2".
+	//
 	// +kubebuilder:validation:Optional
 	// +kubebuilder:validation:Enum="v1";"v2"
 	// +kubebuilder:default:="v2"
@@ -69,11 +73,21 @@ type OpenBaoProvider struct {
 //
 // +kubebuilder:validation:MaxProperties=1
 type OpenBaoAuth struct {
+	// AppRole authenticates with OpenBao using the [App Role auth mechanism],
+	// with the role and secret stored in a Kubernetes Secret resource.
+	//
+	// [App Role auth mechanism]: https://openbao.org/docs/auth/approle/
+	//
+	// +optional
+	AppRole *OpenBaoAppRole `json:"appRole,omitempty"`
+
 	// TokenSecretRef authenticates with OpenBao by presenting a token.
+	//
 	// +optional
 	TokenSecretRef *esmeta.SecretKeySelector `json:"tokenSecretRef,omitempty"`
 
 	// UserPass authenticates with OpenBao by passing a username/password pair
+	//
 	// +optional
 	UserPass *OpenBaoUserPassAuth `json:"userPass,omitempty"`
 }
@@ -86,6 +100,7 @@ type OpenBaoAuth struct {
 type OpenBaoUserPassAuth struct {
 	// Path where the UserPassword authentication backend is mounted
 	// in OpenBao, e.g: "userpass"
+	//
 	// +kubebuilder:default=userpass
 	Path string `json:"path"`
 
@@ -102,3 +117,40 @@ type OpenBaoUserPassAuth struct {
 	// [UserPass authentication method]: https://openbao.org/docs/auth/userpass/
 	SecretRef esmeta.SecretKeySelector `json:"secretRef,omitempty"`
 }
+
+// OpenBaoAppRole authenticates with OpenBao using the [App Role auth
+// mechanism], with the role and secret stored in a Kubernetes Secret resource.
+// The role ID has to be specified either inline via `roleId` or by referencing
+// a secret via `roleRef`.
+//
+// +kubebuilder:validation:ExactlyOneOf=roleId;roleRef
+//
+// [App Role auth mechanism]: https://openbao.org/docs/auth/approle/
+type OpenBaoAppRole struct {
+	// Path where the App Role authentication backend is mounted
+	// in OpenBao, e.g: "approle"
+	//
+	// +kubebuilder:default=approle
+	Path string `json:"path"`
+
+	// RoleID configured in the App Role authentication backend when setting
+	// up the authentication backend in OpenBao.
+	//
+	// +optional
+	// +kubebuilder:validation:MinLength=1
+	RoleID string `json:"roleId,omitempty"`
+
+	// Reference to a key in a Secret that contains the App Role ID used
+	// to authenticate with OpenBao.
+	// The `key` field must be specified and denotes which entry within the Secret
+	// resource is used as the app role id.
+	//
+	// +optional
+	RoleRef *esmeta.SecretKeySelector `json:"roleRef,omitempty"`
+
+	// Reference to a key in a Secret that contains the App Role secret used
+	// to authenticate with OpenBao.
+	// The `key` field must be specified and denotes which entry within the Secret
+	// resource is used as the app role secret.
+	SecretRef esmeta.SecretKeySelector `json:"secretRef"`
+}

+ 26 - 0
apis/externalsecrets/v1/zz_generated.deepcopy.go

@@ -3026,9 +3026,35 @@ func (in *OnePasswordSDKProvider) DeepCopy() *OnePasswordSDKProvider {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *OpenBaoAppRole) DeepCopyInto(out *OpenBaoAppRole) {
+	*out = *in
+	if in.RoleRef != nil {
+		in, out := &in.RoleRef, &out.RoleRef
+		*out = new(apismetav1.SecretKeySelector)
+		(*in).DeepCopyInto(*out)
+	}
+	in.SecretRef.DeepCopyInto(&out.SecretRef)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenBaoAppRole.
+func (in *OpenBaoAppRole) DeepCopy() *OpenBaoAppRole {
+	if in == nil {
+		return nil
+	}
+	out := new(OpenBaoAppRole)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *OpenBaoAuth) DeepCopyInto(out *OpenBaoAuth) {
 	*out = *in
+	if in.AppRole != nil {
+		in, out := &in.AppRole, &out.AppRole
+		*out = new(OpenBaoAppRole)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.TokenSecretRef != nil {
 		in, out := &in.TokenSecretRef, &out.TokenSecretRef
 		*out = new(apismetav1.SecretKeySelector)

+ 90 - 0
config/crds/bases/external-secrets.io_clustersecretstores.yaml

@@ -4182,6 +4182,96 @@ spec:
                           with the OpenBao server.
                         maxProperties: 1
                         properties:
+                          appRole:
+                            description: |-
+                              AppRole authenticates with OpenBao using the [App Role auth mechanism],
+                              with the role and secret stored in a Kubernetes Secret resource.
+
+                              [App Role auth mechanism]: https://openbao.org/docs/auth/approle/
+                            properties:
+                              path:
+                                default: approle
+                                description: |-
+                                  Path where the App Role authentication backend is mounted
+                                  in OpenBao, e.g: "approle"
+                                type: string
+                              roleId:
+                                description: |-
+                                  RoleID configured in the App Role authentication backend when setting
+                                  up the authentication backend in OpenBao.
+                                minLength: 1
+                                type: string
+                              roleRef:
+                                description: |-
+                                  Reference to a key in a Secret that contains the App Role ID used
+                                  to authenticate with OpenBao.
+                                  The `key` field must be specified and denotes which entry within the Secret
+                                  resource is used as the app role id.
+                                properties:
+                                  key:
+                                    description: |-
+                                      A key in the referenced Secret.
+                                      Some instances of this field may be defaulted, in others it may be required.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[-._a-zA-Z0-9]+$
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      The namespace of the Secret resource being referred to.
+                                      Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                    maxLength: 63
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                    type: string
+                                type: object
+                              secretRef:
+                                description: |-
+                                  Reference to a key in a Secret that contains the App Role secret used
+                                  to authenticate with OpenBao.
+                                  The `key` field must be specified and denotes which entry within the Secret
+                                  resource is used as the app role secret.
+                                properties:
+                                  key:
+                                    description: |-
+                                      A key in the referenced Secret.
+                                      Some instances of this field may be defaulted, in others it may be required.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[-._a-zA-Z0-9]+$
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      The namespace of the Secret resource being referred to.
+                                      Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                    maxLength: 63
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                    type: string
+                                type: object
+                            required:
+                            - path
+                            - secretRef
+                            type: object
+                            x-kubernetes-validations:
+                            - message: exactly one of the fields in [roleId roleRef]
+                                must be set
+                              rule: '[has(self.roleId),has(self.roleRef)].filter(x,x==true).size()
+                                == 1'
                           tokenSecretRef:
                             description: TokenSecretRef authenticates with OpenBao
                               by presenting a token.

+ 90 - 0
config/crds/bases/external-secrets.io_secretstores.yaml

@@ -4182,6 +4182,96 @@ spec:
                           with the OpenBao server.
                         maxProperties: 1
                         properties:
+                          appRole:
+                            description: |-
+                              AppRole authenticates with OpenBao using the [App Role auth mechanism],
+                              with the role and secret stored in a Kubernetes Secret resource.
+
+                              [App Role auth mechanism]: https://openbao.org/docs/auth/approle/
+                            properties:
+                              path:
+                                default: approle
+                                description: |-
+                                  Path where the App Role authentication backend is mounted
+                                  in OpenBao, e.g: "approle"
+                                type: string
+                              roleId:
+                                description: |-
+                                  RoleID configured in the App Role authentication backend when setting
+                                  up the authentication backend in OpenBao.
+                                minLength: 1
+                                type: string
+                              roleRef:
+                                description: |-
+                                  Reference to a key in a Secret that contains the App Role ID used
+                                  to authenticate with OpenBao.
+                                  The `key` field must be specified and denotes which entry within the Secret
+                                  resource is used as the app role id.
+                                properties:
+                                  key:
+                                    description: |-
+                                      A key in the referenced Secret.
+                                      Some instances of this field may be defaulted, in others it may be required.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[-._a-zA-Z0-9]+$
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      The namespace of the Secret resource being referred to.
+                                      Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                    maxLength: 63
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                    type: string
+                                type: object
+                              secretRef:
+                                description: |-
+                                  Reference to a key in a Secret that contains the App Role secret used
+                                  to authenticate with OpenBao.
+                                  The `key` field must be specified and denotes which entry within the Secret
+                                  resource is used as the app role secret.
+                                properties:
+                                  key:
+                                    description: |-
+                                      A key in the referenced Secret.
+                                      Some instances of this field may be defaulted, in others it may be required.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[-._a-zA-Z0-9]+$
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      The namespace of the Secret resource being referred to.
+                                      Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                    maxLength: 63
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                    type: string
+                                type: object
+                            required:
+                            - path
+                            - secretRef
+                            type: object
+                            x-kubernetes-validations:
+                            - message: exactly one of the fields in [roleId roleRef]
+                                must be set
+                              rule: '[has(self.roleId),has(self.roleRef)].filter(x,x==true).size()
+                                == 1'
                           tokenSecretRef:
                             description: TokenSecretRef authenticates with OpenBao
                               by presenting a token.

+ 172 - 0
deploy/crds/bundle.yaml

@@ -6165,6 +6165,92 @@ spec:
                           description: Auth configures how secret-manager authenticates with the OpenBao server.
                           maxProperties: 1
                           properties:
+                            appRole:
+                              description: |-
+                                AppRole authenticates with OpenBao using the [App Role auth mechanism],
+                                with the role and secret stored in a Kubernetes Secret resource.
+
+                                [App Role auth mechanism]: https://openbao.org/docs/auth/approle/
+                              properties:
+                                path:
+                                  default: approle
+                                  description: |-
+                                    Path where the App Role authentication backend is mounted
+                                    in OpenBao, e.g: "approle"
+                                  type: string
+                                roleId:
+                                  description: |-
+                                    RoleID configured in the App Role authentication backend when setting
+                                    up the authentication backend in OpenBao.
+                                  minLength: 1
+                                  type: string
+                                roleRef:
+                                  description: |-
+                                    Reference to a key in a Secret that contains the App Role ID used
+                                    to authenticate with OpenBao.
+                                    The `key` field must be specified and denotes which entry within the Secret
+                                    resource is used as the app role id.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        A key in the referenced Secret.
+                                        Some instances of this field may be defaulted, in others it may be required.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[-._a-zA-Z0-9]+$
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        The namespace of the Secret resource being referred to.
+                                        Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                      maxLength: 63
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                      type: string
+                                  type: object
+                                secretRef:
+                                  description: |-
+                                    Reference to a key in a Secret that contains the App Role secret used
+                                    to authenticate with OpenBao.
+                                    The `key` field must be specified and denotes which entry within the Secret
+                                    resource is used as the app role secret.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        A key in the referenced Secret.
+                                        Some instances of this field may be defaulted, in others it may be required.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[-._a-zA-Z0-9]+$
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        The namespace of the Secret resource being referred to.
+                                        Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                      maxLength: 63
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                      type: string
+                                  type: object
+                              required:
+                                - path
+                                - secretRef
+                              type: object
+                              x-kubernetes-validations:
+                                - message: exactly one of the fields in [roleId roleRef] must be set
+                                  rule: '[has(self.roleId),has(self.roleRef)].filter(x,x==true).size() == 1'
                             tokenSecretRef:
                               description: TokenSecretRef authenticates with OpenBao by presenting a token.
                               properties:
@@ -18747,6 +18833,92 @@ spec:
                           description: Auth configures how secret-manager authenticates with the OpenBao server.
                           maxProperties: 1
                           properties:
+                            appRole:
+                              description: |-
+                                AppRole authenticates with OpenBao using the [App Role auth mechanism],
+                                with the role and secret stored in a Kubernetes Secret resource.
+
+                                [App Role auth mechanism]: https://openbao.org/docs/auth/approle/
+                              properties:
+                                path:
+                                  default: approle
+                                  description: |-
+                                    Path where the App Role authentication backend is mounted
+                                    in OpenBao, e.g: "approle"
+                                  type: string
+                                roleId:
+                                  description: |-
+                                    RoleID configured in the App Role authentication backend when setting
+                                    up the authentication backend in OpenBao.
+                                  minLength: 1
+                                  type: string
+                                roleRef:
+                                  description: |-
+                                    Reference to a key in a Secret that contains the App Role ID used
+                                    to authenticate with OpenBao.
+                                    The `key` field must be specified and denotes which entry within the Secret
+                                    resource is used as the app role id.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        A key in the referenced Secret.
+                                        Some instances of this field may be defaulted, in others it may be required.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[-._a-zA-Z0-9]+$
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        The namespace of the Secret resource being referred to.
+                                        Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                      maxLength: 63
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                      type: string
+                                  type: object
+                                secretRef:
+                                  description: |-
+                                    Reference to a key in a Secret that contains the App Role secret used
+                                    to authenticate with OpenBao.
+                                    The `key` field must be specified and denotes which entry within the Secret
+                                    resource is used as the app role secret.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        A key in the referenced Secret.
+                                        Some instances of this field may be defaulted, in others it may be required.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[-._a-zA-Z0-9]+$
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        The namespace of the Secret resource being referred to.
+                                        Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                      maxLength: 63
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                      type: string
+                                  type: object
+                              required:
+                                - path
+                                - secretRef
+                              type: object
+                              x-kubernetes-validations:
+                                - message: exactly one of the fields in [roleId roleRef] must be set
+                                  rule: '[has(self.roleId),has(self.roleRef)].filter(x,x==true).size() == 1'
                             tokenSecretRef:
                               description: TokenSecretRef authenticates with OpenBao by presenting a token.
                               properties:

+ 95 - 0
docs/api/spec.md

@@ -8312,6 +8312,86 @@ cache: {} is a valid option to set.</p>
 </tr>
 </tbody>
 </table>
+<h3 id="external-secrets.io/v1.OpenBaoAppRole">OpenBaoAppRole
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1.OpenBaoAuth">OpenBaoAuth</a>)
+</p>
+<p>
+<p>OpenBaoAppRole authenticates with OpenBao using the <a href="https://openbao.org/docs/auth/approle/">App Role auth
+mechanism</a>, with the role and secret stored in a Kubernetes Secret resource.
+The role ID has to be specified either inline via <code>roleId</code> or by referencing
+a secret via <code>roleRef</code>.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>path</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<p>Path where the App Role authentication backend is mounted
+in OpenBao, e.g: &ldquo;approle&rdquo;</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>roleId</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>RoleID configured in the App Role authentication backend when setting
+up the authentication backend in OpenBao.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>roleRef</code></br>
+<em>
+<a href="https://pkg.go.dev/github.com/external-secrets/external-secrets/apis/meta/v1#SecretKeySelector">
+External Secrets meta/v1.SecretKeySelector
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>Reference to a key in a Secret that contains the App Role ID used
+to authenticate with OpenBao.
+The <code>key</code> field must be specified and denotes which entry within the Secret
+resource is used as the app role id.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>secretRef</code></br>
+<em>
+<a href="https://pkg.go.dev/github.com/external-secrets/external-secrets/apis/meta/v1#SecretKeySelector">
+External Secrets meta/v1.SecretKeySelector
+</a>
+</em>
+</td>
+<td>
+<p>Reference to a key in a Secret that contains the App Role secret used
+to authenticate with OpenBao.
+The <code>key</code> field must be specified and denotes which entry within the Secret
+resource is used as the app role secret.</p>
+</td>
+</tr>
+</tbody>
+</table>
 <h3 id="external-secrets.io/v1.OpenBaoAuth">OpenBaoAuth
 </h3>
 <p>
@@ -8333,6 +8413,21 @@ Additional authentication methods are planned for future releases.</p>
 <tbody>
 <tr>
 <td>
+<code>appRole</code></br>
+<em>
+<a href="#external-secrets.io/v1.OpenBaoAppRole">
+OpenBaoAppRole
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>AppRole authenticates with OpenBao using the <a href="https://openbao.org/docs/auth/approle/">App Role auth mechanism</a>,
+with the role and secret stored in a Kubernetes Secret resource.</p>
+</td>
+</tr>
+<tr>
+<td>
 <code>tokenSecretRef</code></br>
 <em>
 <a href="https://pkg.go.dev/github.com/external-secrets/external-secrets/apis/meta/v1#SecretKeySelector">

+ 1 - 0
go.mod

@@ -294,6 +294,7 @@ require (
 	github.com/nebius/gosdk v0.0.0-20260204094009-511fd4d4f7a1 // indirect
 	github.com/ngrok/ngrok-api-go/v9 v9.0.0 // indirect
 	github.com/oapi-codegen/runtime v1.1.2 // indirect
+	github.com/openbao/openbao/api/auth/approle/v2 v2.5.1 // indirect
 	github.com/openbao/openbao/api/auth/userpass/v2 v2.5.1 // indirect
 	github.com/openbao/openbao/api/v2 v2.5.1-0.20260603121413-a08669ff09ec // indirect
 	github.com/ovh/okms-sdk-go v0.5.1 // indirect

+ 2 - 0
go.sum

@@ -857,6 +857,8 @@ github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je4
 github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
 github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=
 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
+github.com/openbao/openbao/api/auth/approle/v2 v2.5.1 h1:lg6EFF3ToQMITV+Kko773JqOEut9tE+P21dP8pgPaTg=
+github.com/openbao/openbao/api/auth/approle/v2 v2.5.1/go.mod h1:MVa7te0xhx07dldhEvCBxr5GZUUKuDrer60EKHdTmtQ=
 github.com/openbao/openbao/api/auth/userpass/v2 v2.5.1 h1:81YQNOT/0wZJtp6zka9KAdUSADShkhocgb35CJH7r28=
 github.com/openbao/openbao/api/auth/userpass/v2 v2.5.1/go.mod h1:uOtBhWrhgDf++LLD+XtdxKzDFv8cHUurnFz6kRw0+nE=
 github.com/openbao/openbao/api/v2 v2.5.1-0.20260603121413-a08669ff09ec h1:Cka9sTUAqBQBtTYSsOvkG99ojxUp1nlhixAbYl1wRYA=

+ 32 - 12
providers/v1/openbao/client.go

@@ -27,7 +27,6 @@ import (
 	"strconv"
 	"time"
 
-	"github.com/openbao/openbao/api/auth/userpass/v2"
 	"github.com/openbao/openbao/api/v2"
 	v1 "k8s.io/api/core/v1"
 	k8sClient "sigs.k8s.io/controller-runtime/pkg/client"
@@ -59,8 +58,8 @@ type client struct {
 	storeKind  string
 }
 
-func (c *client) setup(ctx context.Context, kube k8sClient.Client, namespace string, httpClient httpClientFactory) error {
-	c.httpClient = httpClient()
+func (c *client) setup(ctx context.Context, kube k8sClient.Client, namespace string, provider *Provider) error {
+	c.httpClient = provider.HTTPClientFactory()
 
 	config := api.DefaultConfig()
 	config.HttpClient = c.httpClient
@@ -99,23 +98,27 @@ func (c *client) setup(ctx context.Context, kube k8sClient.Client, namespace str
 	}
 	c.client = client
 
-	return c.setupAuth(ctx, kube, namespace)
+	return c.setupAuth(ctx, kube, namespace, provider)
 }
 
-func (c *client) setupAuth(ctx context.Context, kube k8sClient.Client, namespace string) error {
+func (c *client) setupAuth(ctx context.Context, kube k8sClient.Client, namespace string, provider *Provider) error {
 	if c.store.Auth == nil {
 		return nil
 	}
 
-	switch {
-	case c.store.Auth.TokenSecretRef != nil:
+	if c.store.Auth.TokenSecretRef != nil {
 		token, err := resolvers.SecretKeyRef(ctx, kube, c.storeKind, namespace, c.store.Auth.TokenSecretRef)
 		if err != nil {
 			return err
 		}
 
 		c.client.SetToken(token)
+		return nil
+	}
+
+	var auth api.AuthMethod
 
+	switch {
 	case c.store.Auth.UserPass != nil:
 		userPass := c.store.Auth.UserPass
 		password, err := resolvers.SecretKeyRef(ctx, kube, c.storeKind, namespace, &userPass.SecretRef)
@@ -123,20 +126,37 @@ func (c *client) setupAuth(ctx context.Context, kube k8sClient.Client, namespace
 			return err
 		}
 
-		auth, err := userpass.NewUserpassAuth(userPass.Username, &userpass.Password{
-			FromString: password,
-		}, userpass.WithMountPath(userPass.Path))
+		auth, err = provider.AuthMethodFactory.UserPass(userPass.Username, password, userPass.Path)
 		if err != nil {
 			return err
 		}
 
-		_, err = c.client.Auth().Login(ctx, auth)
+	case c.store.Auth.AppRole != nil:
+		appRole := c.store.Auth.AppRole
+		secret, err := resolvers.SecretKeyRef(ctx, kube, c.storeKind, namespace, &appRole.SecretRef)
 		if err != nil {
 			return err
 		}
+
+		roleID := appRole.RoleID
+		if appRole.RoleRef != nil { // RoleID and RoleRef are mutually exclusive (enforced by CRD validation)
+			roleID, err = resolvers.SecretKeyRef(ctx, kube, c.storeKind, namespace, appRole.RoleRef)
+			if err != nil {
+				return err
+			}
+		}
+
+		auth, err = provider.AuthMethodFactory.AppRole(roleID, secret, appRole.Path)
+		if err != nil {
+			return err
+		}
+
+	default:
+		return fmt.Errorf("unsupported auth method") // this should not happen, because of CRD validation (unless a case is missing above)
 	}
 
-	return nil
+	_, err := c.client.Auth().Login(ctx, auth)
+	return err
 }
 
 func (c *client) Close(_ context.Context) error {

+ 1 - 0
providers/v1/openbao/go.mod

@@ -7,6 +7,7 @@ require (
 	github.com/external-secrets/external-secrets/runtime v0.0.0-00010101000000-000000000000
 	github.com/go-viper/mapstructure/v2 v2.5.0
 	github.com/onsi/gomega v1.39.1
+	github.com/openbao/openbao/api/auth/approle/v2 v2.5.1
 	github.com/openbao/openbao/api/auth/userpass/v2 v2.5.1
 	github.com/openbao/openbao/api/v2 v2.5.1-0.20260603121413-a08669ff09ec
 	gopkg.in/dnaeon/go-vcr.v4 v4.0.6

+ 2 - 0
providers/v1/openbao/go.sum

@@ -162,6 +162,8 @@ github.com/onsi/ginkgo/v2 v2.28.0 h1:Rrf+lVLmtlBIKv6KrIGJCjyY8N36vDVcutbGJkyqjJc
 github.com/onsi/ginkgo/v2 v2.28.0/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
 github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
 github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=
+github.com/openbao/openbao/api/auth/approle/v2 v2.5.1 h1:lg6EFF3ToQMITV+Kko773JqOEut9tE+P21dP8pgPaTg=
+github.com/openbao/openbao/api/auth/approle/v2 v2.5.1/go.mod h1:MVa7te0xhx07dldhEvCBxr5GZUUKuDrer60EKHdTmtQ=
 github.com/openbao/openbao/api/auth/userpass/v2 v2.5.1 h1:81YQNOT/0wZJtp6zka9KAdUSADShkhocgb35CJH7r28=
 github.com/openbao/openbao/api/auth/userpass/v2 v2.5.1/go.mod h1:uOtBhWrhgDf++LLD+XtdxKzDFv8cHUurnFz6kRw0+nE=
 github.com/openbao/openbao/api/v2 v2.5.1-0.20260603121413-a08669ff09ec h1:Cka9sTUAqBQBtTYSsOvkG99ojxUp1nlhixAbYl1wRYA=

+ 42 - 0
providers/v1/openbao/internal/auth/impl.go

@@ -0,0 +1,42 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package auth
+
+import (
+	"github.com/openbao/openbao/api/auth/approle/v2"
+	"github.com/openbao/openbao/api/auth/userpass/v2"
+	"github.com/openbao/openbao/api/v2"
+)
+
+type authMethodFactory struct{}
+
+// AppRole implements [Factory].
+func (authMethodFactory) AppRole(id, secret, mount string) (api.AuthMethod, error) {
+	return approle.NewAppRoleAuth(id, &approle.SecretID{
+		FromString: secret,
+	}, approle.WithMountPath(mount))
+}
+
+// UserPass implements [Factory].
+func (authMethodFactory) UserPass(username, password, mount string) (api.AuthMethod, error) {
+	return userpass.NewUserpassAuth(username, &userpass.Password{
+		FromString: password,
+	}, userpass.WithMountPath(mount))
+}
+
+// DefaultAuthMethodFactory implements [Factory].
+var DefaultAuthMethodFactory Factory = authMethodFactory{}

+ 36 - 0
providers/v1/openbao/internal/auth/interface.go

@@ -0,0 +1,36 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package auth provides a minimal, mockable interface to OpenBao auth methods.
+//
+// For provider internal use only, API might change at any point in time.
+package auth
+
+import (
+	"github.com/openbao/openbao/api/v2"
+)
+
+// Factory provides a minimal, mockable interface to OpenBao auth methods
+// (minimal in a sense that it exposes exactly the parameters that are needed by
+// the provider). There are two implementation available:
+//
+// - [DefaultAuthMethodFactory]
+//
+// - [MockFactory] a mock for testing.
+type Factory interface {
+	UserPass(username, password, mount string) (api.AuthMethod, error)
+	AppRole(id, secret, mount string) (api.AuthMethod, error)
+}

+ 75 - 0
providers/v1/openbao/internal/auth/mock.go

@@ -0,0 +1,75 @@
+/*
+Copyright © The ESO Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package auth
+
+import (
+	"context"
+	"fmt"
+	"slices"
+	"sync"
+
+	"github.com/openbao/openbao/api/v2"
+)
+
+// MockFactory is a mock implementation of [Factory].
+// All factory methods return mock auth methods, which return a static dummy token.
+// All calls are recorded and can be validated using [MockFactory.GetCalls].
+type MockFactory struct {
+	lock  sync.RWMutex
+	calls []string
+}
+
+func (a *MockFactory) callf(format string, args ...any) {
+	a.lock.Lock()
+	defer a.lock.Unlock()
+	a.calls = append(a.calls, fmt.Sprintf(format, args...))
+}
+
+// AppRole implements [Factory].
+func (a *MockFactory) AppRole(id, secret, mount string) (api.AuthMethod, error) {
+	a.callf("AppRole(%q, %q, %q)", id, secret, mount)
+	return mockAuth{}, nil
+}
+
+// UserPass implements [Factory].
+func (a *MockFactory) UserPass(username, password, mount string) (api.AuthMethod, error) {
+	a.callf("UserPass(%q, %q, %q)", username, password, mount)
+	return mockAuth{}, nil
+}
+
+// GetCalls returns a list of all calls made to the mock (serialized as string), e.g.:
+//
+//	UserPass("user", "password", "mount")
+func (a *MockFactory) GetCalls() []string {
+	a.lock.Lock()
+	defer a.lock.Unlock()
+	return slices.Clone(a.calls)
+}
+
+var _ Factory = &MockFactory{}
+
+type mockAuth struct{}
+
+func (m mockAuth) Login(_ context.Context, _ *api.Client) (*api.Secret, error) {
+	return &api.Secret{
+		Auth: &api.SecretAuth{
+			ClientToken: "mock",
+		},
+	}, nil
+}
+
+var _ api.AuthMethod = mockAuth{}

+ 16 - 1
providers/v1/openbao/provider.go

@@ -26,6 +26,7 @@ import (
 	k8sClient "sigs.k8s.io/controller-runtime/pkg/client"
 
 	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	"github.com/external-secrets/external-secrets/providers/v1/openbao/internal/auth"
 )
 
 var (
@@ -35,6 +36,7 @@ var (
 // Provider implements the ESO Provider interface for OpenBao.
 type Provider struct {
 	HTTPClientFactory httpClientFactory
+	AuthMethodFactory auth.Factory
 }
 
 type httpClientFactory func() *http.Client
@@ -54,7 +56,7 @@ func (p *Provider) NewClient(ctx context.Context, store esv1.GenericStore, kube
 	}
 
 	if client.storeKind != esv1.ClusterSecretStoreKind || namespace != "" || !isReferentSpec(spec) {
-		err := client.setup(ctx, kube, namespace, p.HTTPClientFactory)
+		err := client.setup(ctx, kube, namespace, p)
 		if err != nil {
 			return nil, err
 		}
@@ -67,6 +69,18 @@ func isReferentSpec(prov *esv1.OpenBaoProvider) bool {
 	if prov.Auth != nil {
 		auth := prov.Auth
 
+		if auth.AppRole != nil {
+			appRole := auth.AppRole
+
+			if appRole.RoleRef != nil && appRole.RoleRef.Namespace == nil {
+				return true
+			}
+
+			if appRole.SecretRef.Namespace == nil {
+				return true
+			}
+		}
+
 		if auth.TokenSecretRef != nil && auth.TokenSecretRef.Namespace == nil {
 			return true
 		}
@@ -91,6 +105,7 @@ func NewProvider() esv1.Provider {
 				Transport: http.DefaultTransport.(*http.Transport).Clone(),
 			}
 		},
+		AuthMethodFactory: auth.DefaultAuthMethodFactory,
 	}
 }
 

+ 86 - 0
providers/v1/openbao/provider_test.go

@@ -41,6 +41,7 @@ import (
 	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
 	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
 	"github.com/external-secrets/external-secrets/providers/v1/openbao"
+	"github.com/external-secrets/external-secrets/providers/v1/openbao/internal/auth"
 
 	. "github.com/onsi/gomega"
 )
@@ -357,6 +358,91 @@ func TestProvider_KVv1(t *testing.T) {
 	})
 }
 
+func TestProvider_Auth(t *testing.T) {
+	RegisterTestingT(t)
+
+	kube := clientfake.NewClientBuilder().WithObjects(&corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "shared-secret",
+			Namespace: "default",
+		},
+		Data: map[string][]byte{
+			"approle-id":        []byte("dynamic-roleid"),
+			"approle-secret":    []byte("the-secret"),
+			"userpass-password": []byte("the-password"),
+		},
+	}).Build()
+	provider := openbao.NewProvider().(*openbao.Provider)
+
+	cases := []struct {
+		name          string
+		auth          *esv1.OpenBaoAuth
+		expectedCalls []string
+	}{{
+		name: "userpass",
+		auth: &esv1.OpenBaoAuth{
+			UserPass: &esv1.OpenBaoUserPassAuth{
+				Path:     "userpasspath",
+				Username: "the-user",
+				SecretRef: esmeta.SecretKeySelector{
+					Name: "shared-secret",
+					Key:  "userpass-password",
+				},
+			},
+		},
+		expectedCalls: []string{`UserPass("the-user", "the-password", "userpasspath")`},
+	}, {
+		name: "approle static",
+		auth: &esv1.OpenBaoAuth{
+			AppRole: &esv1.OpenBaoAppRole{
+				Path:   "approlepath",
+				RoleID: "static-roleid",
+				SecretRef: esmeta.SecretKeySelector{
+					Name: "shared-secret",
+					Key:  "approle-secret",
+				},
+			},
+		},
+		expectedCalls: []string{`AppRole("static-roleid", "the-secret", "approlepath")`},
+	}, {
+		name: "approle dynamic",
+		auth: &esv1.OpenBaoAuth{
+			AppRole: &esv1.OpenBaoAppRole{
+				Path: "approlepath",
+				RoleRef: &esmeta.SecretKeySelector{
+					Name: "shared-secret",
+					Key:  "approle-id",
+				},
+				SecretRef: esmeta.SecretKeySelector{
+					Name: "shared-secret",
+					Key:  "approle-secret",
+				},
+			},
+		},
+		expectedCalls: []string{`AppRole("dynamic-roleid", "the-secret", "approlepath")`},
+	}}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			RegisterTestingT(t)
+			factory := &auth.MockFactory{}
+			provider.AuthMethodFactory = factory
+
+			store := makeValidSecretStoreWithVersion(esv1.OpenBaoKVStoreV2)
+			store.Spec.Provider.OpenBao.Auth = tc.auth
+
+			client, err := provider.NewClient(t.Context(), store, kube, "default")
+			Expect(err).NotTo(HaveOccurred())
+			Expect(client).NotTo(BeNil())
+			t.Cleanup(func() {
+				client.Close(t.Context())
+			})
+
+			Expect(factory.GetCalls()).To(Equal(tc.expectedCalls))
+		})
+	}
+}
+
 func TestProvider_Auth_UserPass(t *testing.T) {
 	RegisterTestingT(t)
 	kube, provider := setupProvider(t, &corev1.Secret{

+ 17 - 8
providers/v1/openbao/validate.go

@@ -27,12 +27,11 @@ import (
 )
 
 const (
-	errInvalidStore             = "invalid store"
-	errInvalidStoreSpec         = "invalid store spec"
-	errInvalidStoreProv         = "invalid store provider"
-	errInvalidOpenBaoProv       = "invalid OpenBao provider"
-	errInvalidTokenRef          = "invalid Auth.TokenSecretRef: %w"
-	errInvalidUserPassSecretRef = "invalid Auth.UserPass.SecretRef: %w"
+	errInvalidStore       = "invalid store"
+	errInvalidStoreSpec   = "invalid store spec"
+	errInvalidStoreProv   = "invalid store provider"
+	errInvalidOpenBaoProv = "invalid OpenBao provider"
+	errInvalidRef         = "invalid %s: %w"
 )
 
 // ValidateStore validates the OpenBao provider configuration in the SecretStore.
@@ -53,14 +52,24 @@ func (p *Provider) ValidateStore(store esv1.GenericStore) (admission.Warnings, e
 	}
 	if baoProvider.Auth != nil {
 		auth := baoProvider.Auth
+		if auth.AppRole != nil {
+			if auth.AppRole.RoleRef != nil {
+				if err := esutils.ValidateReferentSecretSelector(store, *auth.AppRole.RoleRef); err != nil {
+					return nil, fmt.Errorf(errInvalidRef, "Auth.AppRole.RoleRef", err)
+				}
+			}
+			if err := esutils.ValidateReferentSecretSelector(store, auth.AppRole.SecretRef); err != nil {
+				return nil, fmt.Errorf(errInvalidRef, "Auth.AppRole.SecretRef", err)
+			}
+		}
 		if auth.TokenSecretRef != nil {
 			if err := esutils.ValidateReferentSecretSelector(store, *auth.TokenSecretRef); err != nil {
-				return nil, fmt.Errorf(errInvalidTokenRef, err)
+				return nil, fmt.Errorf(errInvalidRef, "Auth.TokenSecretRef", err)
 			}
 		}
 		if auth.UserPass != nil {
 			if err := esutils.ValidateReferentSecretSelector(store, auth.UserPass.SecretRef); err != nil {
-				return nil, fmt.Errorf(errInvalidUserPassSecretRef, err)
+				return nil, fmt.Errorf(errInvalidRef, "Auth.UserPass.SecretRef", err)
 			}
 		}
 	}

+ 11 - 0
tests/__snapshot__/clustersecretstore-v1.yaml

@@ -620,6 +620,17 @@ spec:
       vault: string
     openBao:
       auth:
+        appRole:
+          path: "approle"
+          roleId: string
+          roleRef:
+            key: string
+            name: string
+            namespace: string
+          secretRef:
+            key: string
+            name: string
+            namespace: string
         tokenSecretRef:
           key: string
           name: string

+ 11 - 0
tests/__snapshot__/secretstore-v1.yaml

@@ -620,6 +620,17 @@ spec:
       vault: string
     openBao:
       auth:
+        appRole:
+          path: "approle"
+          roleId: string
+          roleRef:
+            key: string
+            name: string
+            namespace: string
+          secretRef:
+            key: string
+            name: string
+            namespace: string
         tokenSecretRef:
           key: string
           name: string