mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
Conjur JWT support (#2591)
* Add JWT Auth to Conjur Provider Signed-off-by: Kieran Bristow <kieran.bristow@absa.africa> * Update docs for Cyberark Conjur Provider Signed-off-by: Kieran Bristow <kieran.bristow@absa.africa> * Update test suite to cover new functionality Signed-off-by: Kieran Bristow <kieran.bristow@absa.africa> * Run make reviewable Signed-off-by: Kieran Bristow <kieran.bristow@absa.africa> * Set MinVersion for tls.Config to satisfy linting Signed-off-by: Kieran Bristow <kieran.bristow@absa.africa> * Move ca bundle config example to a yaml snippet Signed-off-by: Kieran Bristow <kieran.bristow@absa.africa> * fix: consolidate naming Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> * fix: consolidate naming Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> * docs: make it a working example Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> * Remove JWT expiration handling logic Signed-off-by: Kieran Bristow <kieran.bristow@absa.africa> * Run make fmt Signed-off-by: Kieran Bristow <kieran.bristow@absa.africa> --------- Signed-off-by: Kieran Bristow <kieran.bristow@absa.africa> Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> Co-authored-by: Moritz Johner <beller.moritz@googlemail.com>
This commit is contained in:
parent
719e8b1c82
commit
d9eaeb40dc
15 changed files with 1400 additions and 120 deletions
|
@ -17,13 +17,19 @@ package v1beta1
|
|||
import esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
|
||||
type ConjurProvider struct {
|
||||
URL string `json:"url"`
|
||||
CABundle string `json:"caBundle,omitempty"`
|
||||
Auth ConjurAuth `json:"auth"`
|
||||
URL string `json:"url"`
|
||||
// +optional
|
||||
CABundle string `json:"caBundle,omitempty"`
|
||||
// +optional
|
||||
CAProvider *CAProvider `json:"caProvider,omitempty"`
|
||||
Auth ConjurAuth `json:"auth"`
|
||||
}
|
||||
|
||||
type ConjurAuth struct {
|
||||
// +optional
|
||||
Apikey *ConjurApikey `json:"apikey"`
|
||||
// +optional
|
||||
Jwt *ConjurJWT `json:"jwt"`
|
||||
}
|
||||
|
||||
type ConjurApikey struct {
|
||||
|
@ -31,3 +37,20 @@ type ConjurApikey struct {
|
|||
UserRef *esmeta.SecretKeySelector `json:"userRef"`
|
||||
APIKeyRef *esmeta.SecretKeySelector `json:"apiKeyRef"`
|
||||
}
|
||||
|
||||
type ConjurJWT struct {
|
||||
Account string `json:"account"`
|
||||
|
||||
// The conjur authn jwt webservice id
|
||||
ServiceID string `json:"serviceID"`
|
||||
|
||||
// Optional SecretRef that refers to a key in a Secret resource containing JWT token to
|
||||
// authenticate with Conjur using the JWT authentication method.
|
||||
// +optional
|
||||
SecretRef *esmeta.SecretKeySelector `json:"secretRef,omitempty"`
|
||||
|
||||
// Optional ServiceAccountRef specifies the Kubernetes service account for which to request
|
||||
// a token for with the `TokenRequest` API.
|
||||
// +optional
|
||||
ServiceAccountRef *esmeta.ServiceAccountSelector `json:"serviceAccountRef"`
|
||||
}
|
||||
|
|
|
@ -672,6 +672,11 @@ func (in *ConjurAuth) DeepCopyInto(out *ConjurAuth) {
|
|||
*out = new(ConjurApikey)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Jwt != nil {
|
||||
in, out := &in.Jwt, &out.Jwt
|
||||
*out = new(ConjurJWT)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConjurAuth.
|
||||
|
@ -684,9 +689,39 @@ func (in *ConjurAuth) DeepCopy() *ConjurAuth {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ConjurJWT) DeepCopyInto(out *ConjurJWT) {
|
||||
*out = *in
|
||||
if in.SecretRef != nil {
|
||||
in, out := &in.SecretRef, &out.SecretRef
|
||||
*out = new(metav1.SecretKeySelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ServiceAccountRef != nil {
|
||||
in, out := &in.ServiceAccountRef, &out.ServiceAccountRef
|
||||
*out = new(metav1.ServiceAccountSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConjurJWT.
|
||||
func (in *ConjurJWT) DeepCopy() *ConjurJWT {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ConjurJWT)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ConjurProvider) DeepCopyInto(out *ConjurProvider) {
|
||||
*out = *in
|
||||
if in.CAProvider != nil {
|
||||
in, out := &in.CAProvider, &out.CAProvider
|
||||
*out = new(CAProvider)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
in.Auth.DeepCopyInto(&out.Auth)
|
||||
}
|
||||
|
||||
|
|
|
@ -2251,11 +2251,98 @@ spec:
|
|||
- apiKeyRef
|
||||
- userRef
|
||||
type: object
|
||||
required:
|
||||
- apikey
|
||||
jwt:
|
||||
properties:
|
||||
account:
|
||||
type: string
|
||||
secretRef:
|
||||
description: Optional SecretRef that refers to a key
|
||||
in a Secret resource containing JWT token to authenticate
|
||||
with Conjur using the JWT authentication method.
|
||||
properties:
|
||||
key:
|
||||
description: The key of the entry in the Secret
|
||||
resource's `data` field to be used. Some instances
|
||||
of this field may be defaulted, in others it
|
||||
may be required.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the Secret resource being
|
||||
referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred
|
||||
to. Ignored if referent is not cluster-scoped.
|
||||
cluster-scoped defaults to the namespace of
|
||||
the referent.
|
||||
type: string
|
||||
type: object
|
||||
serviceAccountRef:
|
||||
description: Optional ServiceAccountRef specifies
|
||||
the Kubernetes service account for which to request
|
||||
a token for with the `TokenRequest` API.
|
||||
properties:
|
||||
audiences:
|
||||
description: Audience specifies the `aud` claim
|
||||
for the service account token If the service
|
||||
account uses a well-known annotation for e.g.
|
||||
IRSA or GCP Workload Identity then this audiences
|
||||
will be appended to the list
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
name:
|
||||
description: The name of the ServiceAccount resource
|
||||
being referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred
|
||||
to. Ignored if referent is not cluster-scoped.
|
||||
cluster-scoped defaults to the namespace of
|
||||
the referent.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
serviceID:
|
||||
description: The conjur authn jwt webservice id
|
||||
type: string
|
||||
required:
|
||||
- account
|
||||
- serviceID
|
||||
type: object
|
||||
type: object
|
||||
caBundle:
|
||||
type: string
|
||||
caProvider:
|
||||
description: Used to provide custom certificate authority
|
||||
(CA) certificates for a secret store. The CAProvider points
|
||||
to a Secret or ConfigMap resource that contains a PEM-encoded
|
||||
certificate.
|
||||
properties:
|
||||
key:
|
||||
description: The key where the CA certificate can be found
|
||||
in the Secret or ConfigMap.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the object located at the provider
|
||||
type.
|
||||
type: string
|
||||
namespace:
|
||||
description: The namespace the Provider type is in. Can
|
||||
only be defined when used in a ClusterSecretStore.
|
||||
type: string
|
||||
type:
|
||||
description: The type of provider to use such as "Secret",
|
||||
or "ConfigMap".
|
||||
enum:
|
||||
- Secret
|
||||
- ConfigMap
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
type: object
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
|
|
|
@ -2251,11 +2251,98 @@ spec:
|
|||
- apiKeyRef
|
||||
- userRef
|
||||
type: object
|
||||
required:
|
||||
- apikey
|
||||
jwt:
|
||||
properties:
|
||||
account:
|
||||
type: string
|
||||
secretRef:
|
||||
description: Optional SecretRef that refers to a key
|
||||
in a Secret resource containing JWT token to authenticate
|
||||
with Conjur using the JWT authentication method.
|
||||
properties:
|
||||
key:
|
||||
description: The key of the entry in the Secret
|
||||
resource's `data` field to be used. Some instances
|
||||
of this field may be defaulted, in others it
|
||||
may be required.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the Secret resource being
|
||||
referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred
|
||||
to. Ignored if referent is not cluster-scoped.
|
||||
cluster-scoped defaults to the namespace of
|
||||
the referent.
|
||||
type: string
|
||||
type: object
|
||||
serviceAccountRef:
|
||||
description: Optional ServiceAccountRef specifies
|
||||
the Kubernetes service account for which to request
|
||||
a token for with the `TokenRequest` API.
|
||||
properties:
|
||||
audiences:
|
||||
description: Audience specifies the `aud` claim
|
||||
for the service account token If the service
|
||||
account uses a well-known annotation for e.g.
|
||||
IRSA or GCP Workload Identity then this audiences
|
||||
will be appended to the list
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
name:
|
||||
description: The name of the ServiceAccount resource
|
||||
being referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred
|
||||
to. Ignored if referent is not cluster-scoped.
|
||||
cluster-scoped defaults to the namespace of
|
||||
the referent.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
serviceID:
|
||||
description: The conjur authn jwt webservice id
|
||||
type: string
|
||||
required:
|
||||
- account
|
||||
- serviceID
|
||||
type: object
|
||||
type: object
|
||||
caBundle:
|
||||
type: string
|
||||
caProvider:
|
||||
description: Used to provide custom certificate authority
|
||||
(CA) certificates for a secret store. The CAProvider points
|
||||
to a Secret or ConfigMap resource that contains a PEM-encoded
|
||||
certificate.
|
||||
properties:
|
||||
key:
|
||||
description: The key where the CA certificate can be found
|
||||
in the Secret or ConfigMap.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the object located at the provider
|
||||
type.
|
||||
type: string
|
||||
namespace:
|
||||
description: The namespace the Provider type is in. Can
|
||||
only be defined when used in a ClusterSecretStore.
|
||||
type: string
|
||||
type:
|
||||
description: The type of provider to use such as "Secret",
|
||||
or "ConfigMap".
|
||||
enum:
|
||||
- Secret
|
||||
- ConfigMap
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
type: object
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
|
|
|
@ -2101,11 +2101,72 @@ spec:
|
|||
- apiKeyRef
|
||||
- userRef
|
||||
type: object
|
||||
required:
|
||||
- apikey
|
||||
jwt:
|
||||
properties:
|
||||
account:
|
||||
type: string
|
||||
secretRef:
|
||||
description: Optional SecretRef that refers to a key in a Secret resource containing JWT token to authenticate with Conjur using the JWT authentication method.
|
||||
properties:
|
||||
key:
|
||||
description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the Secret resource being referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
|
||||
type: string
|
||||
type: object
|
||||
serviceAccountRef:
|
||||
description: Optional ServiceAccountRef specifies the Kubernetes service account for which to request a token for with the `TokenRequest` API.
|
||||
properties:
|
||||
audiences:
|
||||
description: Audience specifies the `aud` claim for the service account token If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity then this audiences will be appended to the list
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
name:
|
||||
description: The name of the ServiceAccount resource being referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
serviceID:
|
||||
description: The conjur authn jwt webservice id
|
||||
type: string
|
||||
required:
|
||||
- account
|
||||
- serviceID
|
||||
type: object
|
||||
type: object
|
||||
caBundle:
|
||||
type: string
|
||||
caProvider:
|
||||
description: Used to provide custom certificate authority (CA) certificates for a secret store. The CAProvider points to a Secret or ConfigMap resource that contains a PEM-encoded certificate.
|
||||
properties:
|
||||
key:
|
||||
description: The key where the CA certificate can be found in the Secret or ConfigMap.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the object located at the provider type.
|
||||
type: string
|
||||
namespace:
|
||||
description: The namespace the Provider type is in. Can only be defined when used in a ClusterSecretStore.
|
||||
type: string
|
||||
type:
|
||||
description: The type of provider to use such as "Secret", or "ConfigMap".
|
||||
enum:
|
||||
- Secret
|
||||
- ConfigMap
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
type: object
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
|
@ -5837,11 +5898,72 @@ spec:
|
|||
- apiKeyRef
|
||||
- userRef
|
||||
type: object
|
||||
required:
|
||||
- apikey
|
||||
jwt:
|
||||
properties:
|
||||
account:
|
||||
type: string
|
||||
secretRef:
|
||||
description: Optional SecretRef that refers to a key in a Secret resource containing JWT token to authenticate with Conjur using the JWT authentication method.
|
||||
properties:
|
||||
key:
|
||||
description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the Secret resource being referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
|
||||
type: string
|
||||
type: object
|
||||
serviceAccountRef:
|
||||
description: Optional ServiceAccountRef specifies the Kubernetes service account for which to request a token for with the `TokenRequest` API.
|
||||
properties:
|
||||
audiences:
|
||||
description: Audience specifies the `aud` claim for the service account token If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity then this audiences will be appended to the list
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
name:
|
||||
description: The name of the ServiceAccount resource being referred to.
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
serviceID:
|
||||
description: The conjur authn jwt webservice id
|
||||
type: string
|
||||
required:
|
||||
- account
|
||||
- serviceID
|
||||
type: object
|
||||
type: object
|
||||
caBundle:
|
||||
type: string
|
||||
caProvider:
|
||||
description: Used to provide custom certificate authority (CA) certificates for a secret store. The CAProvider points to a Secret or ConfigMap resource that contains a PEM-encoded certificate.
|
||||
properties:
|
||||
key:
|
||||
description: The key where the CA certificate can be found in the Secret or ConfigMap.
|
||||
type: string
|
||||
name:
|
||||
description: The name of the object located at the provider type.
|
||||
type: string
|
||||
namespace:
|
||||
description: The namespace the Provider type is in. Can only be defined when used in a ClusterSecretStore.
|
||||
type: string
|
||||
type:
|
||||
description: The type of provider to use such as "Secret", or "ConfigMap".
|
||||
enum:
|
||||
- Secret
|
||||
- ConfigMap
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
type: object
|
||||
url:
|
||||
type: string
|
||||
required:
|
||||
|
|
|
@ -964,6 +964,7 @@ string
|
|||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#external-secrets.io/v1beta1.AkeylessProvider">AkeylessProvider</a>,
|
||||
<a href="#external-secrets.io/v1beta1.ConjurProvider">ConjurProvider</a>,
|
||||
<a href="#external-secrets.io/v1beta1.KubernetesServer">KubernetesServer</a>,
|
||||
<a href="#external-secrets.io/v1beta1.VaultProvider">VaultProvider</a>)
|
||||
</p>
|
||||
|
@ -1728,6 +1729,89 @@ ConjurApikey
|
|||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>jwt</code></br>
|
||||
<em>
|
||||
<a href="#external-secrets.io/v1beta1.ConjurJWT">
|
||||
ConjurJWT
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3 id="external-secrets.io/v1beta1.ConjurJWT">ConjurJWT
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#external-secrets.io/v1beta1.ConjurAuth">ConjurAuth</a>)
|
||||
</p>
|
||||
<p>
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>account</code></br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>serviceID</code></br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>The conjur authn jwt webservice 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>
|
||||
<em>(Optional)</em>
|
||||
<p>Optional SecretRef that refers to a key in a Secret resource containing JWT token to
|
||||
authenticate with Conjur using the JWT authentication method.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>serviceAccountRef</code></br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/github.com/external-secrets/external-secrets/apis/meta/v1#ServiceAccountSelector">
|
||||
External Secrets meta/v1.ServiceAccountSelector
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Optional ServiceAccountRef specifies the Kubernetes service account for which to request
|
||||
a token for with the <code>TokenRequest</code> API.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@ -1766,6 +1850,20 @@ string
|
|||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>caProvider</code></br>
|
||||
<em>
|
||||
<a href="#external-secrets.io/v1beta1.CAProvider">
|
||||
CAProvider
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
@ -9,32 +9,32 @@ This section contains the list of the pre-requirements before installing the Con
|
|||
* Running Conjur Server
|
||||
- These items will be needed in order to configure the secret-store
|
||||
+ Conjur endpoint - include the scheme but no trailing '/', ex: https://myapi.example.com
|
||||
+ Conjur credentials (hostid, apikey)
|
||||
+ Conjur authentication info (hostid, apikey, jwt service id, etc)
|
||||
+ Conjur must be configured to support your authentication method (`apikey` is supported by default, `jwt` requires additional configuration)
|
||||
+ Certificate for Conjur server is OPTIONAL -- But, **when using a self-signed cert when setting up your Conjur server, it is strongly recommended to populate "caBundle" with self-signed cert in the secret-store definition**
|
||||
* Kubernetes cluster
|
||||
- External Secrets Operator is installed
|
||||
|
||||
### Create External Secret Store Definition
|
||||
### Certificate for Conjur server
|
||||
|
||||
When using a self-signed cert when setting up your Conjur server, it is strongly recommended to populate "caBundle" with self-signed cert in the secret-store definition. The certificate CA must be referenced on the secret-store definition using either a `caBundle` or `caProvider` as below:
|
||||
|
||||
```yaml
|
||||
{% include 'conjur-ca-bundle.yaml' %}
|
||||
```
|
||||
|
||||
### External Secret Store Definition with ApiKey Authentication
|
||||
This method uses a combination of the Conjur `hostid` and `apikey` to authenticate to Conjur. This method is the simplest to setup and use as your Conjur instance requires no special setup.
|
||||
|
||||
#### Create External Secret Store Definition
|
||||
Recommend to save as filename: `conjur-secret-store.yaml`
|
||||
|
||||
```yaml
|
||||
{% include 'conjur-secret-store.yaml' %}
|
||||
{% include 'conjur-secret-store-apikey.yaml' %}
|
||||
```
|
||||
|
||||
### Create External Secret Definition
|
||||
|
||||
Important note: **Creds must live in the same namespace as a SecretStore - the secret store may only reference secrets from the same namespace.** When using a ClusterSecretStore this limitation is lifted and the creds can live in any namespace.
|
||||
|
||||
Recommend to save as filename: `conjur-external-secret.yaml`
|
||||
|
||||
```yaml
|
||||
{% include 'conjur-external-secret.yaml' %}
|
||||
```
|
||||
|
||||
### Create Kubernetes Secrets
|
||||
|
||||
In order for the ESO **Conjur** provider to connect to the Conjur server, the creds should be stored as k8s secrets. Please refer to <https://kubernetes.io/docs/concepts/configuration/secret/#creating-a-secret> for various methods to create secrets. Here is one way to do it using `kubectl`
|
||||
#### Create Kubernetes Secrets
|
||||
In order for the ESO **Conjur** provider to connect to the Conjur server using the `apikey` creds, these creds should be stored as k8s secrets. Please refer to <https://kubernetes.io/docs/concepts/configuration/secret/#creating-a-secret> for various methods to create secrets. Here is one way to do it using `kubectl`
|
||||
|
||||
***NOTE***: "conjur-creds" is the "name" used in "userRef" and "apikeyRef" in the conjur-secret-store definition
|
||||
|
||||
|
@ -46,6 +46,53 @@ kubectl -n external-secrets create secret generic conjur-creds --from-literal=ho
|
|||
# kubectl -n external-secrets create secret generic conjur-creds --from-literal=hostid=host/data/app1/host001 --from-literal=apikey=321blahblah
|
||||
```
|
||||
|
||||
### External Secret Store with JWT Authentication
|
||||
This method uses JWT tokens to authenticate with Conjur. The following methods for retrieving the JWT token for authentication are supported:
|
||||
|
||||
- JWT token from a referenced Kubernetes Service Account
|
||||
- JWT token stored in a Kubernetes secret
|
||||
|
||||
#### Create External Secret Store Definition
|
||||
|
||||
When using JWT authentication the following must be specified in the `SecretStore`:
|
||||
|
||||
- `account` - The name of the Conjur account
|
||||
- `serviceId` - The ID of the JWT Authenticator `WebService` configured in Conjur that will be used to authenticate the JWT token
|
||||
|
||||
You can then choose to either retrieve the JWT token using a Service Account reference or from a Kubernetes Secret.
|
||||
|
||||
To use a JWT token from a referenced Kubernetes Service Account, the following secret store definition can be used:
|
||||
|
||||
```yaml
|
||||
{% include 'conjur-secret-store-jwt-service-account-ref.yaml' %}
|
||||
```
|
||||
|
||||
This is only supported in Kubernetes 1.22 and above as it uses the [TokenRequest API](https://kubernetes.io/docs/reference/kubernetes-api/authentication-resources/token-request-v1/) to get the JWT token from the referenced service account. Audiences can be set as required by the [Conjur JWT authenticator](https://docs.conjur.org/Latest/en/Content/Integrations/k8s-ocp/k8s-jwt-authn.htm).
|
||||
|
||||
Alternatively, a secret containing a valid JWT token can be referenced as follows:
|
||||
|
||||
```yaml
|
||||
{% include 'conjur-secret-store-jwt-secret-ref.yaml' %}
|
||||
```
|
||||
|
||||
This secret must contain a JWT token that identifies your Conjur host. The secret must contain a JWT token consumable by a configured Conjur JWT authenticator and must satisfy all [Conjur JWT guidelines](https://docs.conjur.org/Latest/en/Content/Operations/Services/cjr-authn-jwt-guidelines.htm#Best). This can be a JWT created by an external JWT issuer or the Kubernetes api server itself. Such a with Kubernetes Service Account token can be created using the below command:
|
||||
|
||||
```shell
|
||||
kubectl create token my-service-account --audience='https://conjur.company.com' --duration=3600s
|
||||
```
|
||||
|
||||
Save the `SecretStore` definition as filename `conjur-secret-store.yaml` as referenced in later steps.
|
||||
|
||||
### Create External Secret Definition
|
||||
|
||||
Important note: **Creds must live in the same namespace as a SecretStore - the secret store may only reference secrets from the same namespace.** When using a ClusterSecretStore this limitation is lifted and the creds can live in any namespace.
|
||||
|
||||
Recommend to save as filename: `conjur-external-secret.yaml`
|
||||
|
||||
```yaml
|
||||
{% include 'conjur-external-secret.yaml' %}
|
||||
```
|
||||
|
||||
### Create the External Secrets Store
|
||||
|
||||
```shell
|
||||
|
|
20
docs/snippets/conjur-ca-bundle.yaml
Normal file
20
docs/snippets/conjur-ca-bundle.yaml
Normal file
|
@ -0,0 +1,20 @@
|
|||
....
|
||||
spec:
|
||||
provider:
|
||||
conjur:
|
||||
# Service URL
|
||||
url: https://myapi.conjur.org
|
||||
|
||||
# [OPTIONAL] base64 encoded string of certificate
|
||||
caBundle: "<base64 encoded cabundle>"
|
||||
|
||||
# [OPTIONAL] caProvider:
|
||||
# Instead of caBundle you can also specify a caProvider
|
||||
# this will retrieve the cert from a Secret or ConfigMap
|
||||
caProvider:
|
||||
type: "Secret" # Can be Secret or ConfigMap
|
||||
name: "<name of secret or configmap>"
|
||||
key: "<key inside secret or configmap>"
|
||||
# namespace is mandatory for ClusterSecretStore and not relevant for SecretStore
|
||||
namespace: "my-cert-secret-namespace"
|
||||
....
|
19
docs/snippets/conjur-secret-store-jwt-secret-ref.yaml
Normal file
19
docs/snippets/conjur-secret-store-jwt-secret-ref.yaml
Normal file
|
@ -0,0 +1,19 @@
|
|||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: SecretStore
|
||||
metadata:
|
||||
name: conjur
|
||||
spec:
|
||||
provider:
|
||||
conjur:
|
||||
# Service URL
|
||||
url: https://myapi.conjur.org
|
||||
# [OPTIONAL] base64 encoded string of certificate
|
||||
caBundle: OPTIONALxFIELDxxxBase64xCertxString==
|
||||
auth:
|
||||
jwt:
|
||||
# conjur account
|
||||
account: conjur
|
||||
serviceID: my-jwt-auth-service # The authn-jwt service ID
|
||||
secretRef: # Secret containing a valid JWT token
|
||||
name: my-jwt-secret
|
||||
key: token
|
|
@ -0,0 +1,21 @@
|
|||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: SecretStore
|
||||
metadata:
|
||||
name: conjur
|
||||
spec:
|
||||
provider:
|
||||
conjur:
|
||||
# Service URL
|
||||
url: https://myapi.conjur.org
|
||||
# [OPTIONAL] base64 encoded string of certificate
|
||||
caBundle: OPTIONALxFIELDxxxBase64xCertxString==
|
||||
auth:
|
||||
jwt:
|
||||
# conjur account
|
||||
account: conjur
|
||||
serviceID: my-jwt-auth-service # The authn-jwt service ID
|
||||
serviceAccountRef: # Service account to retrieve JWT token for
|
||||
name: my-service-account
|
||||
audiences: # [OPTIONAL] audiences to include in JWT token
|
||||
- https://conjur.company.com
|
||||
|
110
pkg/provider/conjur/auth_jwt.go
Normal file
110
pkg/provider/conjur/auth_jwt.go
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
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
|
||||
|
||||
http://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 conjur
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/cyberark/conjur-api-go/conjurapi"
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
)
|
||||
|
||||
const JwtLifespan = 600 // 10 minutes
|
||||
|
||||
// getJWTToken retrieves a JWT token either using the TokenRequest API for a specified service account, or from a jwt stored in a k8s secret.
|
||||
func (p *Client) getJWTToken(ctx context.Context, conjurJWTConfig *esv1beta1.ConjurJWT) (string, error) {
|
||||
if conjurJWTConfig.ServiceAccountRef != nil {
|
||||
// Should work for Kubernetes >=v1.22: fetch token via TokenRequest API
|
||||
jwtToken, err := p.getJwtFromServiceAccountTokenRequest(ctx, *conjurJWTConfig.ServiceAccountRef, nil, JwtLifespan)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return jwtToken, nil
|
||||
} else if conjurJWTConfig.SecretRef != nil {
|
||||
tokenRef := conjurJWTConfig.SecretRef
|
||||
if tokenRef.Key == "" {
|
||||
tokenRef = conjurJWTConfig.SecretRef.DeepCopy()
|
||||
tokenRef.Key = "token"
|
||||
}
|
||||
jwtToken, err := p.secretKeyRef(ctx, tokenRef)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return jwtToken, nil
|
||||
}
|
||||
return "", fmt.Errorf("missing ServiceAccountRef or SecretRef")
|
||||
}
|
||||
|
||||
// getJwtFromServiceAccountTokenRequest uses the TokenRequest API to get a JWT token for the given service account.
|
||||
func (p *Client) getJwtFromServiceAccountTokenRequest(ctx context.Context, serviceAccountRef esmeta.ServiceAccountSelector, additionalAud []string, expirationSeconds int64) (string, error) {
|
||||
audiences := serviceAccountRef.Audiences
|
||||
if len(additionalAud) > 0 {
|
||||
audiences = append(audiences, additionalAud...)
|
||||
}
|
||||
tokenRequest := &authenticationv1.TokenRequest{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: p.namespace,
|
||||
},
|
||||
Spec: authenticationv1.TokenRequestSpec{
|
||||
Audiences: audiences,
|
||||
ExpirationSeconds: &expirationSeconds,
|
||||
},
|
||||
}
|
||||
if (p.StoreKind == esv1beta1.ClusterSecretStoreKind) &&
|
||||
(serviceAccountRef.Namespace != nil) {
|
||||
tokenRequest.Namespace = *serviceAccountRef.Namespace
|
||||
}
|
||||
tokenResponse, err := p.corev1.ServiceAccounts(tokenRequest.Namespace).CreateToken(ctx, serviceAccountRef.Name, tokenRequest, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(errGetKubeSATokenRequest, serviceAccountRef.Name, err)
|
||||
}
|
||||
return tokenResponse.Status.Token, nil
|
||||
}
|
||||
|
||||
// newClientFromJwt creates a new Conjur client using the given JWT Auth Config.
|
||||
func (p *Client) newClientFromJwt(ctx context.Context, config conjurapi.Config, jwtAuth *esv1beta1.ConjurJWT) (SecretsClient, error) {
|
||||
jwtToken, getJWTError := p.getJWTToken(ctx, jwtAuth)
|
||||
if getJWTError != nil {
|
||||
return nil, getJWTError
|
||||
}
|
||||
|
||||
client, clientError := p.clientAPI.NewClientFromJWT(config, jwtToken, jwtAuth.ServiceID)
|
||||
if clientError != nil {
|
||||
return nil, clientError
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// newHTTPSClient creates a new HTTPS client with the given cert.
|
||||
func newHTTPSClient(cert []byte) (*http.Client, error) {
|
||||
pool := x509.NewCertPool()
|
||||
ok := pool.AppendCertsFromPEM(cert)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("can't append Conjur SSL cert")
|
||||
}
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{RootCAs: pool, MinVersion: tls.VersionTLS12},
|
||||
}
|
||||
return &http.Client{Transport: tr, Timeout: time.Second * 10}, nil
|
||||
}
|
85
pkg/provider/conjur/conjur_api.go
Normal file
85
pkg/provider/conjur/conjur_api.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
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
|
||||
|
||||
http://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 conjur
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cyberark/conjur-api-go/conjurapi"
|
||||
"github.com/cyberark/conjur-api-go/conjurapi/authn"
|
||||
"github.com/cyberark/conjur-api-go/conjurapi/response"
|
||||
)
|
||||
|
||||
// SecretsClient is an interface for the Conjur client.
|
||||
type SecretsClient interface {
|
||||
RetrieveSecret(secret string) (result []byte, err error)
|
||||
}
|
||||
|
||||
// SecretsClientFactory is an interface for creating a Conjur client.
|
||||
type SecretsClientFactory interface {
|
||||
NewClientFromKey(config conjurapi.Config, loginPair authn.LoginPair) (SecretsClient, error)
|
||||
NewClientFromJWT(config conjurapi.Config, jwtToken string, jwtServiceID string) (SecretsClient, error)
|
||||
}
|
||||
|
||||
// ClientAPIImpl is an implementation of the ClientAPI interface.
|
||||
type ClientAPIImpl struct{}
|
||||
|
||||
func (c *ClientAPIImpl) NewClientFromKey(config conjurapi.Config, loginPair authn.LoginPair) (SecretsClient, error) {
|
||||
return conjurapi.NewClientFromKey(config, loginPair)
|
||||
}
|
||||
|
||||
// NewClientFromJWT creates a new Conjur client from a JWT token.
|
||||
// cannot use the built-in function "conjurapi.NewClientFromJwt" because it requires environment variables
|
||||
// see: https://github.com/cyberark/conjur-api-go/blob/b698692392a38e5d38b8440f32ab74206544848a/conjurapi/client.go#L130
|
||||
func (c *ClientAPIImpl) NewClientFromJWT(config conjurapi.Config, jwtToken, jwtServiceID string) (SecretsClient, error) {
|
||||
jwtTokenString := fmt.Sprintf("jwt=%s", jwtToken)
|
||||
|
||||
var httpClient *http.Client
|
||||
if config.IsHttps() {
|
||||
cert, err := config.ReadSSLCert()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
httpClient, err = newHTTPSClient(cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
httpClient = &http.Client{Timeout: time.Second * 10}
|
||||
}
|
||||
|
||||
authnJwtURL := strings.Join([]string{config.ApplianceURL, "authn-jwt", jwtServiceID, config.Account, "authenticate"}, "/")
|
||||
|
||||
req, err := http.NewRequest("POST", authnJwtURL, strings.NewReader(jwtTokenString))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
tokenBytes, err := response.DataResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conjurapi.NewClientFromToken(config, string(tokenBytes))
|
||||
}
|
|
@ -24,7 +24,10 @@ import (
|
|||
"github.com/cyberark/conjur-api-go/conjurapi/authn"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
ctrlcfg "sigs.k8s.io/controller-runtime/pkg/client/config"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
|
@ -37,76 +40,130 @@ var (
|
|||
errBadCertBundle = "caBundle failed to base64 decode: %w"
|
||||
errBadServiceUser = "could not get Auth.Apikey.UserRef: %w"
|
||||
errBadServiceAPIKey = "could not get Auth.Apikey.ApiKeyRef: %w"
|
||||
|
||||
errGetKubeSATokenRequest = "cannot request Kubernetes service account token for service account %q: %w"
|
||||
|
||||
errUnableToFetchCAProviderCM = "unable to fetch Server.CAProvider ConfigMap: %w"
|
||||
errUnableToFetchCAProviderSecret = "unable to fetch Server.CAProvider Secret: %w"
|
||||
)
|
||||
|
||||
// Provider is a provider for Conjur.
|
||||
type Provider struct {
|
||||
ConjurClient Client
|
||||
StoreKind string
|
||||
kube client.Client
|
||||
namespace string
|
||||
// Client is a provider for Conjur.
|
||||
type Client struct {
|
||||
StoreKind string
|
||||
kube client.Client
|
||||
store esv1beta1.GenericStore
|
||||
namespace string
|
||||
corev1 typedcorev1.CoreV1Interface
|
||||
clientAPI SecretsClientFactory
|
||||
client SecretsClient
|
||||
}
|
||||
|
||||
// Client is an interface for the Conjur client.
|
||||
type Client interface {
|
||||
RetrieveSecret(secret string) (result []byte, err error)
|
||||
type Provider struct {
|
||||
NewConjurProvider func(context context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string, corev1 typedcorev1.CoreV1Interface, clientApi SecretsClientFactory) (esv1beta1.SecretsClient, error)
|
||||
}
|
||||
|
||||
// NewClient creates a new Conjur client.
|
||||
func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
prov, err := util.GetConjurProvider(store)
|
||||
func (c *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
// controller-runtime/client does not support TokenRequest or other subresource APIs
|
||||
// so we need to construct our own client and use it to create a TokenRequest
|
||||
restCfg, err := ctrlcfg.GetConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.StoreKind = store.GetObjectKind().GroupVersionKind().Kind
|
||||
p.kube = kube
|
||||
p.namespace = namespace
|
||||
|
||||
certBytes, decodeErr := utils.Decode(esv1beta1.ExternalSecretDecodeBase64, []byte(prov.CABundle))
|
||||
if decodeErr != nil {
|
||||
return nil, fmt.Errorf(errBadCertBundle, decodeErr)
|
||||
clientset, err := kubernetes.NewForConfig(restCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.NewConjurProvider(ctx, store, kube, namespace, clientset.CoreV1(), &ClientAPIImpl{})
|
||||
}
|
||||
|
||||
func newConjurProvider(_ context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string, corev1 typedcorev1.CoreV1Interface, clientAPI SecretsClientFactory) (esv1beta1.SecretsClient, error) {
|
||||
return &Client{
|
||||
StoreKind: store.GetObjectKind().GroupVersionKind().Kind,
|
||||
store: store,
|
||||
kube: kube,
|
||||
namespace: namespace,
|
||||
corev1: corev1,
|
||||
clientAPI: clientAPI,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Client) GetConjurClient(ctx context.Context) (SecretsClient, error) {
|
||||
// if the client is initialized already, return it
|
||||
if p.client != nil {
|
||||
return p.client, nil
|
||||
}
|
||||
|
||||
prov, err := util.GetConjurProvider(p.store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert, getCertErr := p.getCA(ctx, prov)
|
||||
if getCertErr != nil {
|
||||
return nil, getCertErr
|
||||
}
|
||||
cert := string(certBytes)
|
||||
|
||||
config := conjurapi.Config{
|
||||
Account: prov.Auth.Apikey.Account,
|
||||
ApplianceURL: prov.URL,
|
||||
SSLCert: cert,
|
||||
}
|
||||
|
||||
conjUser, secErr := p.secretKeyRef(ctx, prov.Auth.Apikey.UserRef)
|
||||
if secErr != nil {
|
||||
return nil, fmt.Errorf(errBadServiceUser, secErr)
|
||||
}
|
||||
conjAPIKey, secErr := p.secretKeyRef(ctx, prov.Auth.Apikey.APIKeyRef)
|
||||
if secErr != nil {
|
||||
return nil, fmt.Errorf(errBadServiceAPIKey, secErr)
|
||||
}
|
||||
if prov.Auth.Apikey != nil {
|
||||
config.Account = prov.Auth.Apikey.Account
|
||||
conjUser, secErr := p.secretKeyRef(ctx, prov.Auth.Apikey.UserRef)
|
||||
if secErr != nil {
|
||||
return nil, fmt.Errorf(errBadServiceUser, secErr)
|
||||
}
|
||||
conjAPIKey, secErr := p.secretKeyRef(ctx, prov.Auth.Apikey.APIKeyRef)
|
||||
if secErr != nil {
|
||||
return nil, fmt.Errorf(errBadServiceAPIKey, secErr)
|
||||
}
|
||||
|
||||
conjur, err := conjurapi.NewClientFromKey(config,
|
||||
authn.LoginPair{
|
||||
Login: conjUser,
|
||||
APIKey: conjAPIKey,
|
||||
},
|
||||
)
|
||||
conjur, newClientFromKeyError := p.clientAPI.NewClientFromKey(config,
|
||||
authn.LoginPair{
|
||||
Login: conjUser,
|
||||
APIKey: conjAPIKey,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errConjurClient, err)
|
||||
if newClientFromKeyError != nil {
|
||||
return nil, fmt.Errorf(errConjurClient, newClientFromKeyError)
|
||||
}
|
||||
p.client = conjur
|
||||
return conjur, nil
|
||||
} else if prov.Auth.Jwt != nil {
|
||||
config.Account = prov.Auth.Jwt.Account
|
||||
|
||||
conjur, clientFromJwtError := p.newClientFromJwt(ctx, config, prov.Auth.Jwt)
|
||||
if clientFromJwtError != nil {
|
||||
return nil, fmt.Errorf(errConjurClient, clientFromJwtError)
|
||||
}
|
||||
|
||||
p.client = conjur
|
||||
|
||||
return conjur, nil
|
||||
} else {
|
||||
// Should not happen because validate func should catch this
|
||||
return nil, fmt.Errorf("no authentication method provided")
|
||||
}
|
||||
p.ConjurClient = conjur
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// GetAllSecrets returns all secrets from the provider.
|
||||
// NOT IMPLEMENTED.
|
||||
func (p *Provider) GetAllSecrets(_ context.Context, _ esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
|
||||
func (p *Client) GetAllSecrets(_ context.Context, _ esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
|
||||
// TO be implemented
|
||||
return nil, fmt.Errorf("GetAllSecrets not implemented")
|
||||
}
|
||||
|
||||
// GetSecret returns a single secret from the provider.
|
||||
func (p *Provider) GetSecret(_ context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
|
||||
secretValue, err := p.ConjurClient.RetrieveSecret(ref.Key)
|
||||
func (p *Client) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
|
||||
conjurClient, getConjurClientError := p.GetConjurClient(ctx)
|
||||
if getConjurClientError != nil {
|
||||
return nil, getConjurClientError
|
||||
}
|
||||
secretValue, err := conjurClient.RetrieveSecret(ref.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -115,18 +172,18 @@ func (p *Provider) GetSecret(_ context.Context, ref esv1beta1.ExternalSecretData
|
|||
}
|
||||
|
||||
// PushSecret will write a single secret into the provider.
|
||||
func (p *Provider) PushSecret(_ context.Context, _ []byte, _ *apiextensionsv1.JSON, _ esv1beta1.PushRemoteRef) error {
|
||||
func (p *Client) PushSecret(_ context.Context, _ []byte, _ *apiextensionsv1.JSON, _ esv1beta1.PushRemoteRef) error {
|
||||
// NOT IMPLEMENTED
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) DeleteSecret(_ context.Context, _ esv1beta1.PushRemoteRef) error {
|
||||
func (p *Client) DeleteSecret(_ context.Context, _ esv1beta1.PushRemoteRef) error {
|
||||
// NOT IMPLEMENTED
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSecretMap returns multiple k/v pairs from the provider.
|
||||
func (p *Provider) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
|
||||
func (p *Client) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
|
||||
// Gets a secret as normal, expecting secret value to be a json object
|
||||
data, err := p.GetSecret(ctx, ref)
|
||||
if err != nil {
|
||||
|
@ -149,17 +206,17 @@ func (p *Provider) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecre
|
|||
}
|
||||
|
||||
// Close closes the provider.
|
||||
func (p *Provider) Close(_ context.Context) error {
|
||||
func (p *Client) Close(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validates the provider.
|
||||
func (p *Provider) Validate() (esv1beta1.ValidationResult, error) {
|
||||
func (p *Client) Validate() (esv1beta1.ValidationResult, error) {
|
||||
return esv1beta1.ValidationResultReady, nil
|
||||
}
|
||||
|
||||
// ValidateStore validates the store.
|
||||
func (p *Provider) ValidateStore(store esv1beta1.GenericStore) error {
|
||||
func (c *Provider) ValidateStore(store esv1beta1.GenericStore) error {
|
||||
prov, err := util.GetConjurProvider(store)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -186,8 +243,30 @@ func (p *Provider) ValidateStore(store esv1beta1.GenericStore) error {
|
|||
}
|
||||
}
|
||||
|
||||
if prov.Auth.Jwt != nil {
|
||||
if prov.Auth.Jwt.Account == "" {
|
||||
return fmt.Errorf("missing Auth.Jwt.Account")
|
||||
}
|
||||
if prov.Auth.Jwt.ServiceID == "" {
|
||||
return fmt.Errorf("missing Auth.Jwt.ServiceID")
|
||||
}
|
||||
if prov.Auth.Jwt.ServiceAccountRef == nil && prov.Auth.Jwt.SecretRef == nil {
|
||||
return fmt.Errorf("must specify Auth.Jwt.SecretRef or Auth.Jwt.ServiceAccountRef")
|
||||
}
|
||||
if prov.Auth.Jwt.SecretRef != nil {
|
||||
if err := utils.ValidateReferentSecretSelector(store, *prov.Auth.Jwt.SecretRef); err != nil {
|
||||
return fmt.Errorf("invalid Auth.Jwt.SecretRef: %w", err)
|
||||
}
|
||||
}
|
||||
if prov.Auth.Jwt.ServiceAccountRef != nil {
|
||||
if err := utils.ValidateReferentServiceAccountSelector(store, *prov.Auth.Jwt.ServiceAccountRef); err != nil {
|
||||
return fmt.Errorf("invalid Auth.Jwt.ServiceAccountRef: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// At least one auth must be configured
|
||||
if prov.Auth.Apikey == nil {
|
||||
if prov.Auth.Apikey == nil && prov.Auth.Jwt == nil {
|
||||
return fmt.Errorf("missing Auth.* configuration")
|
||||
}
|
||||
|
||||
|
@ -195,11 +274,11 @@ func (p *Provider) ValidateStore(store esv1beta1.GenericStore) error {
|
|||
}
|
||||
|
||||
// Capabilities returns the provider Capabilities (Read, Write, ReadWrite).
|
||||
func (p *Provider) Capabilities() esv1beta1.SecretStoreCapabilities {
|
||||
func (c *Provider) Capabilities() esv1beta1.SecretStoreCapabilities {
|
||||
return esv1beta1.SecretStoreReadOnly
|
||||
}
|
||||
|
||||
func (p *Provider) secretKeyRef(ctx context.Context, secretRef *esmeta.SecretKeySelector) (string, error) {
|
||||
func (p *Client) secretKeyRef(ctx context.Context, secretRef *esmeta.SecretKeySelector) (string, error) {
|
||||
secret := &corev1.Secret{}
|
||||
ref := client.ObjectKey{
|
||||
Namespace: p.namespace,
|
||||
|
@ -224,8 +303,71 @@ func (p *Provider) secretKeyRef(ctx context.Context, secretRef *esmeta.SecretKey
|
|||
return valueStr, nil
|
||||
}
|
||||
|
||||
// configMapKeyRef returns the value of a key in a configmap.
|
||||
func (p *Client) configMapKeyRef(ctx context.Context, cmRef *esmeta.SecretKeySelector) (string, error) {
|
||||
configMap := &corev1.ConfigMap{}
|
||||
ref := client.ObjectKey{
|
||||
Namespace: p.namespace,
|
||||
Name: cmRef.Name,
|
||||
}
|
||||
if (p.StoreKind == esv1beta1.ClusterSecretStoreKind) &&
|
||||
(cmRef.Namespace != nil) {
|
||||
ref.Namespace = *cmRef.Namespace
|
||||
}
|
||||
err := p.kube.Get(ctx, ref, configMap)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
keyBytes, ok := configMap.Data[cmRef.Key]
|
||||
if !ok {
|
||||
return "", err
|
||||
}
|
||||
|
||||
valueStr := strings.TrimSpace(keyBytes)
|
||||
return valueStr, nil
|
||||
}
|
||||
|
||||
// getCA try retrieve the CA bundle from the provider CABundle or from the CAProvider.
|
||||
func (p *Client) getCA(ctx context.Context, provider *esv1beta1.ConjurProvider) (string, error) {
|
||||
if provider.CAProvider != nil {
|
||||
var ca string
|
||||
var err error
|
||||
switch provider.CAProvider.Type {
|
||||
case esv1beta1.CAProviderTypeConfigMap:
|
||||
keySelector := esmeta.SecretKeySelector{
|
||||
Name: provider.CAProvider.Name,
|
||||
Namespace: provider.CAProvider.Namespace,
|
||||
Key: provider.CAProvider.Key,
|
||||
}
|
||||
ca, err = p.configMapKeyRef(ctx, &keySelector)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(errUnableToFetchCAProviderCM, err)
|
||||
}
|
||||
case esv1beta1.CAProviderTypeSecret:
|
||||
keySelector := esmeta.SecretKeySelector{
|
||||
Name: provider.CAProvider.Name,
|
||||
Namespace: provider.CAProvider.Namespace,
|
||||
Key: provider.CAProvider.Key,
|
||||
}
|
||||
ca, err = p.secretKeyRef(ctx, &keySelector)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(errUnableToFetchCAProviderSecret, err)
|
||||
}
|
||||
}
|
||||
return ca, nil
|
||||
}
|
||||
certBytes, decodeErr := utils.Decode(esv1beta1.ExternalSecretDecodeBase64, []byte(provider.CABundle))
|
||||
if decodeErr != nil {
|
||||
return "", fmt.Errorf(errBadCertBundle, decodeErr)
|
||||
}
|
||||
return string(certBytes), nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
esv1beta1.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
|
||||
esv1beta1.Register(&Provider{
|
||||
NewConjurProvider: newConjurProvider,
|
||||
}, &esv1beta1.SecretStoreProvider{
|
||||
Conjur: &esv1beta1.ConjurProvider{},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -16,12 +16,26 @@ package conjur
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cyberark/conjur-api-go/conjurapi"
|
||||
"github.com/cyberark/conjur-api-go/conjurapi/authn"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
kclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
fakeconjur "github.com/external-secrets/external-secrets/pkg/provider/conjur/fake"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/conjur/fake"
|
||||
utilfake "github.com/external-secrets/external-secrets/pkg/provider/util/fake"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -31,39 +45,6 @@ var (
|
|||
svcAccount = "account1"
|
||||
)
|
||||
|
||||
type secretManagerTestCase struct {
|
||||
err error
|
||||
refKey string
|
||||
}
|
||||
|
||||
func TestConjurGetSecret(t *testing.T) {
|
||||
p := Provider{}
|
||||
p.ConjurClient = &fakeconjur.ConjurMockClient{}
|
||||
|
||||
testCases := []*secretManagerTestCase{
|
||||
{
|
||||
err: nil,
|
||||
refKey: "secret",
|
||||
},
|
||||
{
|
||||
err: fmt.Errorf("error"),
|
||||
refKey: "error",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
ref := makeValidRef(tc.refKey)
|
||||
_, err := p.GetSecret(context.Background(), *ref)
|
||||
if tc.err != nil && err != nil && err.Error() != tc.err.Error() {
|
||||
t.Errorf("test failed! want %v, got %v", tc.err, err)
|
||||
} else if tc.err == nil && err != nil {
|
||||
t.Errorf("want nil got err %v", err)
|
||||
} else if tc.err != nil && err == nil {
|
||||
t.Errorf("want err %v got nil", tc.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeValidRef(k string) *esv1beta1.ExternalSecretDataRemoteRef {
|
||||
return &esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: k,
|
||||
|
@ -79,29 +60,59 @@ type ValidateStoreTestCase struct {
|
|||
func TestValidateStore(t *testing.T) {
|
||||
testCases := []ValidateStoreTestCase{
|
||||
{
|
||||
store: makeSecretStore(svcURL, svcUser, svcApikey, svcAccount),
|
||||
store: makeAPIKeySecretStore(svcURL, svcUser, svcApikey, svcAccount),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
store: makeSecretStore("", svcUser, svcApikey, svcAccount),
|
||||
store: makeAPIKeySecretStore("", svcUser, svcApikey, svcAccount),
|
||||
err: fmt.Errorf("conjur URL cannot be empty"),
|
||||
},
|
||||
{
|
||||
store: makeSecretStore(svcURL, "", svcApikey, svcAccount),
|
||||
store: makeAPIKeySecretStore(svcURL, "", svcApikey, svcAccount),
|
||||
err: fmt.Errorf("missing Auth.Apikey.UserRef"),
|
||||
},
|
||||
{
|
||||
store: makeSecretStore(svcURL, svcUser, "", svcAccount),
|
||||
store: makeAPIKeySecretStore(svcURL, svcUser, "", svcAccount),
|
||||
err: fmt.Errorf("missing Auth.Apikey.ApiKeyRef"),
|
||||
},
|
||||
{
|
||||
store: makeSecretStore(svcURL, svcUser, svcApikey, ""),
|
||||
store: makeAPIKeySecretStore(svcURL, svcUser, svcApikey, ""),
|
||||
err: fmt.Errorf("missing Auth.ApiKey.Account"),
|
||||
},
|
||||
|
||||
{
|
||||
store: makeJWTSecretStore(svcURL, "conjur", "", "jwt-auth-service", "myconjuraccount"),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
store: makeJWTSecretStore(svcURL, "", "jwt-secret", "jwt-auth-service", "myconjuraccount"),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
store: makeJWTSecretStore(svcURL, "conjur", "", "jwt-auth-service", ""),
|
||||
err: fmt.Errorf("missing Auth.Jwt.Account"),
|
||||
},
|
||||
{
|
||||
store: makeJWTSecretStore(svcURL, "conjur", "", "", "myconjuraccount"),
|
||||
err: fmt.Errorf("missing Auth.Jwt.ServiceID"),
|
||||
},
|
||||
{
|
||||
store: makeJWTSecretStore("", "conjur", "", "jwt-auth-service", "myconjuraccount"),
|
||||
err: fmt.Errorf("conjur URL cannot be empty"),
|
||||
},
|
||||
{
|
||||
store: makeJWTSecretStore(svcURL, "", "", "jwt-auth-service", "myconjuraccount"),
|
||||
err: fmt.Errorf("must specify Auth.Jwt.SecretRef or Auth.Jwt.ServiceAccountRef"),
|
||||
},
|
||||
|
||||
{
|
||||
store: makeNoAuthSecretStore(svcURL),
|
||||
err: fmt.Errorf("missing Auth.* configuration"),
|
||||
},
|
||||
}
|
||||
p := Provider{}
|
||||
c := Provider{}
|
||||
for _, tc := range testCases {
|
||||
err := p.ValidateStore(tc.store)
|
||||
err := c.ValidateStore(tc.store)
|
||||
if tc.err != nil && err != nil && err.Error() != tc.err.Error() {
|
||||
t.Errorf("test failed! want %v, got %v", tc.err, err)
|
||||
} else if tc.err == nil && err != nil {
|
||||
|
@ -112,7 +123,214 @@ func TestValidateStore(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func makeSecretStore(svcURL, svcUser, svcApikey, svcAccount string) *esv1beta1.SecretStore {
|
||||
func TestGetSecret(t *testing.T) {
|
||||
type args struct {
|
||||
store esv1beta1.GenericStore
|
||||
kube kclient.Client
|
||||
corev1 typedcorev1.CoreV1Interface
|
||||
namespace string
|
||||
secretPath string
|
||||
}
|
||||
|
||||
type want struct {
|
||||
err error
|
||||
value string
|
||||
}
|
||||
|
||||
type testCase struct {
|
||||
reason string
|
||||
args args
|
||||
want want
|
||||
}
|
||||
|
||||
cases := map[string]testCase{
|
||||
"ApiKeyReadSecretSuccess": {
|
||||
reason: "Should read a secret successfully using an ApiKey auth secret store.",
|
||||
args: args{
|
||||
store: makeAPIKeySecretStore(svcURL, "conjur-hostid", "conjur-apikey", "myconjuraccount"),
|
||||
kube: clientfake.NewClientBuilder().
|
||||
WithObjects(makeFakeAPIKeySecrets()...).Build(),
|
||||
namespace: "default",
|
||||
secretPath: "path/to/secret",
|
||||
},
|
||||
want: want{
|
||||
err: nil,
|
||||
value: "secret",
|
||||
},
|
||||
},
|
||||
"ApiKeyReadSecretFailure": {
|
||||
reason: "Should fail to read secret using ApiKey auth secret store.",
|
||||
args: args{
|
||||
store: makeAPIKeySecretStore(svcURL, "conjur-hostid", "conjur-apikey", "myconjuraccount"),
|
||||
kube: clientfake.NewClientBuilder().
|
||||
WithObjects(makeFakeAPIKeySecrets()...).Build(),
|
||||
namespace: "default",
|
||||
secretPath: "error",
|
||||
},
|
||||
want: want{
|
||||
err: errors.New("error"),
|
||||
value: "",
|
||||
},
|
||||
},
|
||||
"JwtWithServiceAccountRefReadSecretSuccess": {
|
||||
reason: "Should read a secret successfully using a JWT auth secret store that references a k8s service account.",
|
||||
args: args{
|
||||
store: makeJWTSecretStore(svcURL, "my-service-account", "", "jwt-authenticator", "myconjuraccount"),
|
||||
kube: clientfake.NewClientBuilder().
|
||||
WithObjects().Build(),
|
||||
namespace: "default",
|
||||
secretPath: "path/to/secret",
|
||||
corev1: utilfake.NewCreateTokenMock().WithToken(createFakeJwtToken(true)),
|
||||
},
|
||||
want: want{
|
||||
err: nil,
|
||||
value: "secret",
|
||||
},
|
||||
},
|
||||
"JwtWithSecretRefReadSecretSuccess": {
|
||||
reason: "Should read a secret successfully using an JWT auth secret store that references a k8s secret.",
|
||||
args: args{
|
||||
store: makeJWTSecretStore(svcURL, "", "jwt-secret", "jwt-authenticator", "myconjuraccount"),
|
||||
kube: clientfake.NewClientBuilder().
|
||||
WithObjects(&corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "jwt-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"token": []byte(createFakeJwtToken(true)),
|
||||
},
|
||||
}).Build(),
|
||||
namespace: "default",
|
||||
secretPath: "path/to/secret",
|
||||
},
|
||||
want: want{
|
||||
err: nil,
|
||||
value: "secret",
|
||||
},
|
||||
},
|
||||
"JwtWithCABundleSuccess": {
|
||||
reason: "Should read a secret successfully using a JWT auth secret store that references a k8s service account.",
|
||||
args: args{
|
||||
store: makeJWTSecretStore(svcURL, "my-service-account", "", "jwt-authenticator", "myconjuraccount"),
|
||||
kube: clientfake.NewClientBuilder().
|
||||
WithObjects().Build(),
|
||||
namespace: "default",
|
||||
secretPath: "path/to/secret",
|
||||
corev1: utilfake.NewCreateTokenMock().WithToken(createFakeJwtToken(true)),
|
||||
},
|
||||
want: want{
|
||||
err: nil,
|
||||
value: "secret",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runTest := func(t *testing.T, _ string, tc testCase) {
|
||||
provider, _ := newConjurProvider(context.Background(), tc.args.store, tc.args.kube, tc.args.namespace, tc.args.corev1, &ConjurMockAPIClient{})
|
||||
ref := makeValidRef(tc.args.secretPath)
|
||||
secret, err := provider.GetSecret(context.Background(), *ref)
|
||||
if diff := cmp.Diff(tc.want.err, err, EquateErrors()); diff != "" {
|
||||
t.Errorf("\n%s\nconjur.GetSecret(...): -want error, +got error:\n%s", tc.reason, diff)
|
||||
}
|
||||
secretString := string(secret)
|
||||
if secretString != tc.want.value {
|
||||
t.Errorf("\n%s\nconjur.GetSecret(...): want value %v got %v", tc.reason, tc.want.value, secretString)
|
||||
}
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
runTest(t, name, tc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCA(t *testing.T) {
|
||||
type args struct {
|
||||
store esv1beta1.GenericStore
|
||||
kube kclient.Client
|
||||
corev1 typedcorev1.CoreV1Interface
|
||||
namespace string
|
||||
}
|
||||
|
||||
type want struct {
|
||||
err error
|
||||
cert string
|
||||
}
|
||||
|
||||
type testCase struct {
|
||||
reason string
|
||||
args args
|
||||
want want
|
||||
}
|
||||
|
||||
certData := "mycertdata"
|
||||
certDataEncoded := "bXljZXJ0ZGF0YQo="
|
||||
|
||||
cases := map[string]testCase{
|
||||
"UseCABundleSuccess": {
|
||||
reason: "Should read a caBundle successfully.",
|
||||
args: args{
|
||||
store: makeStoreWithCA("cabundle", certDataEncoded),
|
||||
kube: clientfake.NewClientBuilder().
|
||||
WithObjects().Build(),
|
||||
namespace: "default",
|
||||
corev1: utilfake.NewCreateTokenMock().WithToken(createFakeJwtToken(true)),
|
||||
},
|
||||
want: want{
|
||||
err: nil,
|
||||
cert: certDataEncoded,
|
||||
},
|
||||
},
|
||||
"UseCAProviderConfigMapSuccess": {
|
||||
reason: "Should read a ca from a ConfigMap successfully.",
|
||||
args: args{
|
||||
store: makeStoreWithCA("configmap", ""),
|
||||
kube: clientfake.NewClientBuilder().
|
||||
WithObjects(makeFakeCASource("configmap", certData)).Build(),
|
||||
namespace: "default",
|
||||
corev1: utilfake.NewCreateTokenMock().WithToken(createFakeJwtToken(true)),
|
||||
},
|
||||
want: want{
|
||||
err: nil,
|
||||
cert: certDataEncoded,
|
||||
},
|
||||
},
|
||||
"UseCAProviderSecretSuccess": {
|
||||
reason: "Should read a ca from a Secret successfully.",
|
||||
args: args{
|
||||
store: makeStoreWithCA("secret", ""),
|
||||
kube: clientfake.NewClientBuilder().
|
||||
WithObjects(makeFakeCASource("secret", certData)).Build(),
|
||||
namespace: "default",
|
||||
corev1: utilfake.NewCreateTokenMock().WithToken(createFakeJwtToken(true)),
|
||||
},
|
||||
want: want{
|
||||
err: nil,
|
||||
cert: certDataEncoded,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runTest := func(t *testing.T, _ string, tc testCase) {
|
||||
provider, _ := newConjurProvider(context.Background(), tc.args.store, tc.args.kube, tc.args.namespace, tc.args.corev1, &ConjurMockAPIClient{})
|
||||
_, err := provider.GetSecret(context.Background(), esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: "path/to/secret",
|
||||
})
|
||||
if diff := cmp.Diff(tc.want.err, err, EquateErrors()); diff != "" {
|
||||
t.Errorf("\n%s\nconjur.GetCA(...): -want error, +got error:\n%s", tc.reason, diff)
|
||||
}
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
runTest(t, name, tc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func makeAPIKeySecretStore(svcURL, svcUser, svcApikey, svcAccount string) *esv1beta1.SecretStore {
|
||||
uref := &esmeta.SecretKeySelector{
|
||||
Name: "user",
|
||||
Key: "conjur-hostid",
|
||||
|
@ -145,3 +363,169 @@ func makeSecretStore(svcURL, svcUser, svcApikey, svcAccount string) *esv1beta1.S
|
|||
}
|
||||
return store
|
||||
}
|
||||
|
||||
func makeJWTSecretStore(svcURL, serviceAccountName, secretName, jwtServiceID, conjurAccount string) *esv1beta1.SecretStore {
|
||||
serviceAccountRef := &esmeta.ServiceAccountSelector{
|
||||
Name: serviceAccountName,
|
||||
Audiences: []string{"conjur"},
|
||||
}
|
||||
if serviceAccountName == "" {
|
||||
serviceAccountRef = nil
|
||||
}
|
||||
|
||||
secretRef := &esmeta.SecretKeySelector{
|
||||
Name: secretName,
|
||||
Key: "token",
|
||||
}
|
||||
if secretName == "" {
|
||||
secretRef = nil
|
||||
}
|
||||
|
||||
store := &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
Conjur: &esv1beta1.ConjurProvider{
|
||||
URL: svcURL,
|
||||
Auth: esv1beta1.ConjurAuth{
|
||||
Jwt: &esv1beta1.ConjurJWT{
|
||||
Account: conjurAccount,
|
||||
ServiceID: jwtServiceID,
|
||||
ServiceAccountRef: serviceAccountRef,
|
||||
SecretRef: secretRef,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return store
|
||||
}
|
||||
|
||||
func makeStoreWithCA(caSource, caData string) *esv1beta1.SecretStore {
|
||||
store := makeJWTSecretStore(svcURL, "conjur", "", "jwt-auth-service", "myconjuraccount")
|
||||
if caSource == "secret" {
|
||||
store.Spec.Provider.Conjur.CAProvider = &esv1beta1.CAProvider{
|
||||
Type: esv1beta1.CAProviderTypeSecret,
|
||||
Name: "conjur-cert",
|
||||
Key: "ca",
|
||||
}
|
||||
} else if caSource == "configmap" {
|
||||
store.Spec.Provider.Conjur.CAProvider = &esv1beta1.CAProvider{
|
||||
Type: esv1beta1.CAProviderTypeConfigMap,
|
||||
Name: "conjur-cert",
|
||||
Key: "ca",
|
||||
}
|
||||
} else {
|
||||
store.Spec.Provider.Conjur.CABundle = caData
|
||||
}
|
||||
return store
|
||||
}
|
||||
|
||||
func makeNoAuthSecretStore(svcURL string) *esv1beta1.SecretStore {
|
||||
store := &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
Conjur: &esv1beta1.ConjurProvider{
|
||||
URL: svcURL,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return store
|
||||
}
|
||||
|
||||
func makeFakeAPIKeySecrets() []kclient.Object {
|
||||
return []kclient.Object{
|
||||
&corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "user",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"conjur-hostid": []byte("myhostid"),
|
||||
},
|
||||
},
|
||||
&corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "apikey",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"conjur-apikey": []byte("apikey"),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func makeFakeCASource(kind, caData string) kclient.Object {
|
||||
if kind == "secret" {
|
||||
return &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "conjur-cert",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"conjur-cert": []byte(caData),
|
||||
},
|
||||
}
|
||||
}
|
||||
return &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "conjur-cert",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"ca": caData,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createFakeJwtToken(expires bool) string {
|
||||
signingKey := []byte("fakekey")
|
||||
token := jwt.New(jwt.SigningMethodHS256)
|
||||
claims := token.Claims.(jwt.MapClaims)
|
||||
if expires {
|
||||
claims["exp"] = time.Now().Add(time.Minute * 30).Unix()
|
||||
}
|
||||
jwtTokenString, err := token.SignedString(signingKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return jwtTokenString
|
||||
}
|
||||
|
||||
// ConjurMockAPIClient is a mock implementation of the ApiClient interface.
|
||||
type ConjurMockAPIClient struct {
|
||||
}
|
||||
|
||||
func (c *ConjurMockAPIClient) NewClientFromKey(_ conjurapi.Config, _ authn.LoginPair) (SecretsClient, error) {
|
||||
return &fake.ConjurMockClient{}, nil
|
||||
}
|
||||
|
||||
func (c *ConjurMockAPIClient) NewClientFromJWT(_ conjurapi.Config, _, _ string) (SecretsClient, error) {
|
||||
return &fake.ConjurMockClient{}, nil
|
||||
}
|
||||
|
||||
// EquateErrors returns true if the supplied errors are of the same type and
|
||||
// produce identical strings. This mirrors the error comparison behavior of
|
||||
// https://github.com/go-test/deep, which most Crossplane tests targeted before
|
||||
// we switched to go-cmp.
|
||||
//
|
||||
// This differs from cmpopts.EquateErrors, which does not test for error strings
|
||||
// and instead returns whether one error 'is' (in the errors.Is sense) the
|
||||
// other.
|
||||
func EquateErrors() cmp.Option {
|
||||
return cmp.Comparer(func(a, b error) bool {
|
||||
if a == nil || b == nil {
|
||||
return a == nil && b == nil
|
||||
}
|
||||
|
||||
av := reflect.ValueOf(a)
|
||||
bv := reflect.ValueOf(b)
|
||||
if av.Type() != bv.Type() {
|
||||
return false
|
||||
}
|
||||
|
||||
return a.Error() == b.Error()
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue