mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
Conjur E2E Tests for K8s JWT Authentication (#3217)
Signed-off-by: Shlomo Heigh <shlomo.heigh@cyberark.com>
This commit is contained in:
parent
91139d07f4
commit
1d3209da59
13 changed files with 391 additions and 59 deletions
|
@ -44,6 +44,11 @@ type ConjurJWT struct {
|
|||
// The conjur authn jwt webservice id
|
||||
ServiceID string `json:"serviceID"`
|
||||
|
||||
// Optional HostID for JWT authentication. This may be used depending
|
||||
// on how the Conjur JWT authenticator policy is configured.
|
||||
// +optional
|
||||
HostID string `json:"hostId"`
|
||||
|
||||
// Optional SecretRef that refers to a key in a Secret resource containing JWT token to
|
||||
// authenticate with Conjur using the JWT authentication method.
|
||||
// +optional
|
||||
|
|
|
@ -2311,6 +2311,11 @@ spec:
|
|||
properties:
|
||||
account:
|
||||
type: string
|
||||
hostId:
|
||||
description: |-
|
||||
Optional HostID for JWT authentication. This may be used depending
|
||||
on how the Conjur JWT authenticator policy is configured.
|
||||
type: string
|
||||
secretRef:
|
||||
description: |-
|
||||
Optional SecretRef that refers to a key in a Secret resource containing JWT token to
|
||||
|
|
|
@ -2311,6 +2311,11 @@ spec:
|
|||
properties:
|
||||
account:
|
||||
type: string
|
||||
hostId:
|
||||
description: |-
|
||||
Optional HostID for JWT authentication. This may be used depending
|
||||
on how the Conjur JWT authenticator policy is configured.
|
||||
type: string
|
||||
secretRef:
|
||||
description: |-
|
||||
Optional SecretRef that refers to a key in a Secret resource containing JWT token to
|
||||
|
|
|
@ -2764,6 +2764,11 @@ spec:
|
|||
properties:
|
||||
account:
|
||||
type: string
|
||||
hostId:
|
||||
description: |-
|
||||
Optional HostID for JWT authentication. This may be used depending
|
||||
on how the Conjur JWT authenticator policy is configured.
|
||||
type: string
|
||||
secretRef:
|
||||
description: |-
|
||||
Optional SecretRef that refers to a key in a Secret resource containing JWT token to
|
||||
|
@ -7918,6 +7923,11 @@ spec:
|
|||
properties:
|
||||
account:
|
||||
type: string
|
||||
hostId:
|
||||
description: |-
|
||||
Optional HostID for JWT authentication. This may be used depending
|
||||
on how the Conjur JWT authenticator policy is configured.
|
||||
type: string
|
||||
secretRef:
|
||||
description: |-
|
||||
Optional SecretRef that refers to a key in a Secret resource containing JWT token to
|
||||
|
|
|
@ -1943,6 +1943,19 @@ string
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>hostId</code></br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Optional HostID for JWT authentication. This may be used depending
|
||||
on how the Conjur JWT authenticator policy is configured.</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">
|
||||
|
|
|
@ -14,8 +14,10 @@ limitations under the License.
|
|||
package addon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
|
@ -51,7 +53,8 @@ func NewConjur(namespace string) *Conjur {
|
|||
Namespace: namespace,
|
||||
ReleaseName: fmt.Sprintf("conjur-%s", namespace), // avoid cluster role collision
|
||||
Chart: fmt.Sprintf("%s/conjur-oss", repo),
|
||||
ChartVersion: "2.0.7",
|
||||
// Use latest version of Conjur OSS. To pin to a specific version, uncomment the following line.
|
||||
// ChartVersion: "2.0.7",
|
||||
Repo: ChartRepo{
|
||||
Name: repo,
|
||||
URL: "https://cyberark.github.io/helm-charts",
|
||||
|
@ -148,10 +151,101 @@ func (l *Conjur) initConjur() error {
|
|||
|
||||
func (l *Conjur) configureConjur() error {
|
||||
ginkgo.By("configuring conjur")
|
||||
// TODO: This will be used for the JWT tests
|
||||
// Construct Conjur policy for authn-jwt. This uses the token-app-property "sub" to
|
||||
// authenticate the host. This means that Conjur will determine which host is authenticating
|
||||
// based on the "sub" claim in the JWT token, which is provided by the Kubernetes service account.
|
||||
policy := `- !policy
|
||||
id: conjur/authn-jwt/eso-tests
|
||||
body:
|
||||
- !webservice
|
||||
- !variable public-keys
|
||||
- !variable issuer
|
||||
- !variable token-app-property
|
||||
- !variable audience`
|
||||
|
||||
_, err := l.ConjurClient.LoadPolicy(conjurapi.PolicyModePost, "root", strings.NewReader(policy))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load authn-jwt policy: %w", err)
|
||||
}
|
||||
|
||||
// Construct Conjur policy for authn-jwt-hostid. This does not use the token-app-property variable
|
||||
// and instead uses the HostID passed in the authentication URL to determine which host is authenticating.
|
||||
// This is not the recommended way to authenticate, but it is needed for certain use cases where the
|
||||
// JWT token does not contain the "sub" claim.
|
||||
policy = `- !policy
|
||||
id: conjur/authn-jwt/eso-tests-hostid
|
||||
body:
|
||||
- !webservice
|
||||
- !variable public-keys
|
||||
- !variable issuer
|
||||
- !variable audience`
|
||||
|
||||
_, err = l.ConjurClient.LoadPolicy(conjurapi.PolicyModePost, "root", strings.NewReader(policy))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load authn-jwt policy: %w", err)
|
||||
}
|
||||
|
||||
// Fetch the jwks info from the k8s cluster
|
||||
pubKeysJson, issuer, err := l.fetchJWKSandIssuer()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to fetch jwks and issuer: %w", err)
|
||||
}
|
||||
|
||||
// Set the variables for the authn-jwt policies
|
||||
secrets := map[string]string{
|
||||
"conjur/authn-jwt/eso-tests/audience": l.ConjurURL,
|
||||
"conjur/authn-jwt/eso-tests/issuer": issuer,
|
||||
"conjur/authn-jwt/eso-tests/public-keys": string(pubKeysJson),
|
||||
"conjur/authn-jwt/eso-tests/token-app-property": "sub",
|
||||
"conjur/authn-jwt/eso-tests-hostid/audience": l.ConjurURL,
|
||||
"conjur/authn-jwt/eso-tests-hostid/issuer": issuer,
|
||||
"conjur/authn-jwt/eso-tests-hostid/public-keys": string(pubKeysJson),
|
||||
}
|
||||
|
||||
for secretPath, secretValue := range secrets {
|
||||
err := l.ConjurClient.AddSecret(secretPath, secretValue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to add secret %s: %w", secretPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Conjur) fetchJWKSandIssuer() (pubKeysJson string, issuer string, err error) {
|
||||
kc := l.chart.config.KubeClientSet
|
||||
|
||||
// Fetch the openid-configuration
|
||||
res, err := kc.CoreV1().RESTClient().Get().AbsPath("/.well-known/openid-configuration").DoRaw(context.Background())
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("unable to fetch openid-configuration: %w", err)
|
||||
}
|
||||
var openidConfig map[string]interface{}
|
||||
json.Unmarshal(res, &openidConfig)
|
||||
issuer = openidConfig["issuer"].(string)
|
||||
|
||||
// Fetch the jwks
|
||||
jwksJson, err := kc.CoreV1().RESTClient().Get().AbsPath("/openid/v1/jwks").DoRaw(context.Background())
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("unable to fetch jwks: %w", err)
|
||||
}
|
||||
var jwks map[string]interface{}
|
||||
json.Unmarshal(jwksJson, &jwks)
|
||||
|
||||
// Create a JSON object with the jwks that can be used by Conjur
|
||||
pubKeysObj := map[string]interface{}{
|
||||
"type": "jwks",
|
||||
"value": jwks,
|
||||
}
|
||||
pubKeysJsonObj, err := json.Marshal(pubKeysObj)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("unable to marshal jwks: %w", err)
|
||||
}
|
||||
|
||||
pubKeysJson = string(pubKeysJsonObj)
|
||||
return pubKeysJson, issuer, nil
|
||||
}
|
||||
|
||||
func (l *Conjur) Logs() error {
|
||||
return l.chart.Logs()
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
authenticators: authn,authn-jwt/eso-tests
|
||||
authenticators: authn,authn-jwt/eso-tests,authn-jwt/eso-tests-hostid
|
||||
logLevel: "debug"
|
||||
service:
|
||||
external:
|
||||
|
|
|
@ -21,8 +21,9 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
withTokenAuth = "with apikey auth"
|
||||
withJWTK8s = "with jwt k8s provider"
|
||||
withTokenAuth = "with apikey auth"
|
||||
withJWTK8s = "with jwt k8s provider"
|
||||
withJWTK8sHostID = "with jwt k8s hostid provider"
|
||||
)
|
||||
|
||||
var _ = Describe("[conjur]", Label("conjur"), func() {
|
||||
|
@ -38,9 +39,17 @@ var _ = Describe("[conjur]", Label("conjur"), func() {
|
|||
framework.Compose(withTokenAuth, f, common.JSONDataFromRewrite, useApiKeyAuth),
|
||||
framework.Compose(withTokenAuth, f, common.SyncV1Alpha1, useApiKeyAuth),
|
||||
|
||||
// // use jwt k8s provider
|
||||
// framework.Compose(withJWTK8s, f, common.JSONDataFromSync, useJWTK8sProvider),
|
||||
// framework.Compose(withJWTK8s, f, common.JSONDataFromRewrite, useJWTK8sProvider),
|
||||
// use jwt k8s provider
|
||||
framework.Compose(withJWTK8s, f, common.SimpleDataSync, useJWTK8sProvider),
|
||||
framework.Compose(withJWTK8s, f, common.SyncWithoutTargetName, useJWTK8sProvider),
|
||||
framework.Compose(withJWTK8s, f, common.JSONDataFromSync, useJWTK8sProvider),
|
||||
framework.Compose(withJWTK8s, f, common.JSONDataFromRewrite, useJWTK8sProvider),
|
||||
|
||||
// use jwt k8s hostid provider
|
||||
framework.Compose(withJWTK8sHostID, f, common.SimpleDataSync, useJWTK8sHostIDProvider),
|
||||
framework.Compose(withJWTK8sHostID, f, common.SyncWithoutTargetName, useJWTK8sHostIDProvider),
|
||||
framework.Compose(withJWTK8sHostID, f, common.JSONDataFromSync, useJWTK8sHostIDProvider),
|
||||
framework.Compose(withJWTK8sHostID, f, common.JSONDataFromRewrite, useJWTK8sHostIDProvider),
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -48,6 +57,10 @@ func useApiKeyAuth(tc *framework.TestCase) {
|
|||
tc.ExternalSecret.Spec.SecretStoreRef.Name = tc.Framework.Namespace.Name
|
||||
}
|
||||
|
||||
// func useJWTK8sProvider(tc *framework.TestCase) {
|
||||
// tc.ExternalSecret.Spec.SecretStoreRef.Name = jwtK8sProviderName
|
||||
// }
|
||||
func useJWTK8sProvider(tc *framework.TestCase) {
|
||||
tc.ExternalSecret.Spec.SecretStoreRef.Name = jwtK8sProviderName
|
||||
}
|
||||
|
||||
func useJWTK8sHostIDProvider(tc *framework.TestCase) {
|
||||
tc.ExternalSecret.Spec.SecretStoreRef.Name = jwtK8sHostIDProviderName
|
||||
}
|
||||
|
|
81
e2e/suites/provider/cases/conjur/policy.go
Normal file
81
e2e/suites/provider/cases/conjur/policy.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
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 (
|
||||
"bytes"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
const createVariablePolicyTemplate = `- !variable
|
||||
id: {{ .Key }}
|
||||
|
||||
- !permit
|
||||
role: !host system:serviceaccount:{{ .Namespace }}:test-app-sa
|
||||
privilege: [ read, execute ]
|
||||
resource: !variable {{ .Key }}
|
||||
|
||||
- !permit
|
||||
role: !host system:serviceaccount:{{ .Namespace }}:test-app-hostid-sa
|
||||
privilege: [ read, execute ]
|
||||
resource: !variable {{ .Key }}`
|
||||
|
||||
const deleteVariablePolicyTemplate = `- !delete
|
||||
record: !variable {{ .Key }}`
|
||||
|
||||
const jwtHostPolicyTemplate = `- !host
|
||||
id: {{ .HostID }}
|
||||
annotations:
|
||||
authn-jwt/{{ .ServiceID }}/sub: "{{ .HostID }}"
|
||||
|
||||
- !permit
|
||||
role: !host {{ .HostID }}
|
||||
privilege: [ read, authenticate ]
|
||||
resource: !webservice conjur/authn-jwt/{{ .ServiceID }}`
|
||||
|
||||
func createVariablePolicy(key, namespace string) string {
|
||||
return renderTemplate(createVariablePolicyTemplate, map[string]string{
|
||||
"Key": key,
|
||||
"Namespace": namespace,
|
||||
})
|
||||
}
|
||||
|
||||
func deleteVariablePolicy(key string) string {
|
||||
return renderTemplate(deleteVariablePolicyTemplate, map[string]string{
|
||||
"Key": key,
|
||||
})
|
||||
}
|
||||
|
||||
func createJwtHostPolicy(hostID, serviceID string) string {
|
||||
return renderTemplate(jwtHostPolicyTemplate, map[string]string{
|
||||
"HostID": hostID,
|
||||
"ServiceID": serviceID,
|
||||
})
|
||||
}
|
||||
|
||||
func renderTemplate(templateText string, data map[string]string) string {
|
||||
// Use golang templates to render the policy
|
||||
tmpl, err := template.New("policy").Parse(templateText)
|
||||
if err != nil {
|
||||
// The templates are hardcoded, so this should never happen
|
||||
panic(err)
|
||||
}
|
||||
output := new(bytes.Buffer)
|
||||
err = tmpl.Execute(output, data)
|
||||
if err != nil {
|
||||
// The templates are hardcoded, so this should never happen
|
||||
panic(err)
|
||||
}
|
||||
return output.String()
|
||||
}
|
|
@ -40,8 +40,8 @@ type conjurProvider struct {
|
|||
}
|
||||
|
||||
const (
|
||||
apiKeyAuthProviderName = "api-key-auth-provider"
|
||||
jwtK8sProviderName = "jwt-k8s-provider"
|
||||
jwtK8sProviderName = "jwt-k8s-provider"
|
||||
jwtK8sHostIDProviderName = "jwt-k8s-hostid-provider"
|
||||
)
|
||||
|
||||
func newConjurProvider(f *framework.Framework) *conjurProvider {
|
||||
|
@ -49,12 +49,14 @@ func newConjurProvider(f *framework.Framework) *conjurProvider {
|
|||
framework: f,
|
||||
}
|
||||
BeforeEach(prov.BeforeEach)
|
||||
AfterEach(prov.AfterEach)
|
||||
return prov
|
||||
}
|
||||
|
||||
func (s *conjurProvider) CreateSecret(key string, val framework.SecretEntry) {
|
||||
// Generate a policy file for the secret key
|
||||
policy := "- !variable " + key
|
||||
policy := createVariablePolicy(key, s.framework.Namespace.Name)
|
||||
|
||||
_, err := s.client.LoadPolicy(conjurapi.PolicyModePost, "root", strings.NewReader(policy))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
|
@ -64,8 +66,7 @@ func (s *conjurProvider) CreateSecret(key string, val framework.SecretEntry) {
|
|||
}
|
||||
|
||||
func (s *conjurProvider) DeleteSecret(key string) {
|
||||
policy := `- !delete
|
||||
record: !variable ` + key
|
||||
policy := deleteVariablePolicy(key)
|
||||
_, err := s.client.LoadPolicy(conjurapi.PolicyModePatch, "root", strings.NewReader(policy))
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
@ -79,7 +80,33 @@ func (s *conjurProvider) BeforeEach() {
|
|||
s.url = c.ConjurURL
|
||||
|
||||
s.CreateApiKeyStore(c, ns)
|
||||
// s.CreateJWTK8sStore(c, ns)
|
||||
s.CreateJWTK8sStore(c, ns)
|
||||
s.CreateJWTK8sHostIDStore(c, ns)
|
||||
}
|
||||
|
||||
func (s *conjurProvider) AfterEach() {
|
||||
// Print Conjur logs if the test failed
|
||||
if !CurrentGinkgoTestDescription().Failed {
|
||||
return
|
||||
}
|
||||
|
||||
// Get logs from Conjur pod
|
||||
ns := s.framework.Namespace.Name
|
||||
pods, err := s.framework.KubeClientSet.CoreV1().Pods(ns).List(context.Background(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
GinkgoWriter.Printf("Error getting pods: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, pod := range pods.Items {
|
||||
if strings.Contains(pod.Name, "conjur-oss") {
|
||||
logs, err := s.framework.KubeClientSet.CoreV1().Pods(ns).GetLogs(pod.Name, &v1.PodLogOptions{Container: "conjur-oss"}).DoRaw(context.Background())
|
||||
if err != nil {
|
||||
GinkgoWriter.Printf("Error getting logs from Conjur pod: %s\n", err)
|
||||
}
|
||||
GinkgoWriter.Printf("Conjur logs:\n%s\n", logs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeStore(name, ns string, c *addon.Conjur) *esv1beta1.SecretStore {
|
||||
|
@ -103,7 +130,6 @@ func (s *conjurProvider) CreateApiKeyStore(c *addon.Conjur, ns string) {
|
|||
By("creating a conjur secret")
|
||||
conjurCreds := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
// Name: apiKeyAuthProviderName,
|
||||
Name: ns,
|
||||
Namespace: ns,
|
||||
},
|
||||
|
@ -116,18 +142,15 @@ func (s *conjurProvider) CreateApiKeyStore(c *addon.Conjur, ns string) {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
By("creating an secret store for conjur")
|
||||
// secretStore := makeStore(apiKeyAuthProviderName, ns, c)
|
||||
secretStore := makeStore(ns, ns, c)
|
||||
secretStore.Spec.Provider.Conjur.Auth = esv1beta1.ConjurAuth{
|
||||
APIKey: &esv1beta1.ConjurAPIKey{
|
||||
Account: "default",
|
||||
UserRef: &esmeta.SecretKeySelector{
|
||||
// Name: apiKeyAuthProviderName,
|
||||
Name: ns,
|
||||
Key: "username",
|
||||
},
|
||||
APIKeyRef: &esmeta.SecretKeySelector{
|
||||
// Name: apiKeyAuthProviderName,
|
||||
Name: ns,
|
||||
Key: "apikey",
|
||||
},
|
||||
|
@ -137,20 +160,77 @@ func (s *conjurProvider) CreateApiKeyStore(c *addon.Conjur, ns string) {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
|
||||
// func (s conjurProvider) CreateJWTK8sStore(c *addon.Conjur, ns string) {
|
||||
// secretStore := makeStore(jwtK8sProviderName, ns, c)
|
||||
// secretStore.Spec.Provider.Conjur.Auth = esv1beta1.ConjurAuth{
|
||||
// Jwt: &esv1beta1.ConjurJWT{
|
||||
// Account: "default",
|
||||
// ServiceID: "eso-tests",
|
||||
// ServiceAccountRef: &esmeta.ServiceAccountSelector{
|
||||
// Name: "default",
|
||||
// Audiences: []string{
|
||||
// c.ConjurURL,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
// err := s.framework.CRClient.Create(context.Background(), secretStore)
|
||||
// Expect(err).ToNot(HaveOccurred())
|
||||
// }
|
||||
func (s conjurProvider) CreateJWTK8sStore(c *addon.Conjur, ns string) {
|
||||
// Create a service account
|
||||
sa := &v1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-app-sa",
|
||||
Namespace: ns,
|
||||
},
|
||||
}
|
||||
err := s.framework.CRClient.Create(context.Background(), sa)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Add the service account to the Conjur policy with permissions to
|
||||
// authenticate with authn-jwt
|
||||
saName := "system:serviceaccount:" + ns + ":test-app-sa"
|
||||
policy := createJwtHostPolicy(saName, "eso-tests")
|
||||
|
||||
_, err = s.client.LoadPolicy(conjurapi.PolicyModePost, "root", strings.NewReader(policy))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Now create a secret store that uses the service account to authenticate
|
||||
secretStore := makeStore(jwtK8sProviderName, ns, c)
|
||||
secretStore.Spec.Provider.Conjur.Auth = esv1beta1.ConjurAuth{
|
||||
Jwt: &esv1beta1.ConjurJWT{
|
||||
Account: "default",
|
||||
ServiceID: "eso-tests",
|
||||
ServiceAccountRef: &esmeta.ServiceAccountSelector{
|
||||
Name: "test-app-sa",
|
||||
Audiences: []string{
|
||||
c.ConjurURL,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
err = s.framework.CRClient.Create(context.Background(), secretStore)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
|
||||
func (s conjurProvider) CreateJWTK8sHostIDStore(c *addon.Conjur, ns string) {
|
||||
// Create a service account
|
||||
sa := &v1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-app-hostid-sa",
|
||||
Namespace: ns,
|
||||
},
|
||||
}
|
||||
err := s.framework.CRClient.Create(context.Background(), sa)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Add the service account to the Conjur policy with permissions to
|
||||
// authenticate with authn-jwt
|
||||
saName := "system:serviceaccount:" + ns + ":test-app-hostid-sa"
|
||||
policy := createJwtHostPolicy(saName, "eso-tests-hostid")
|
||||
|
||||
_, err = s.client.LoadPolicy(conjurapi.PolicyModePost, "root", strings.NewReader(policy))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Now create a secret store that uses the service account to authenticate
|
||||
secretStore := makeStore(jwtK8sHostIDProviderName, ns, c)
|
||||
secretStore.Spec.Provider.Conjur.Auth = esv1beta1.ConjurAuth{
|
||||
Jwt: &esv1beta1.ConjurJWT{
|
||||
Account: "default",
|
||||
HostID: "host/" + saName,
|
||||
ServiceID: "eso-tests-hostid",
|
||||
ServiceAccountRef: &esmeta.ServiceAccountSelector{
|
||||
Name: "test-app-hostid-sa",
|
||||
Audiences: []string{
|
||||
c.ConjurURL,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
err = s.framework.CRClient.Create(context.Background(), secretStore)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@ func (p *Client) newClientFromJwt(ctx context.Context, config conjurapi.Config,
|
|||
return nil, getJWTError
|
||||
}
|
||||
|
||||
client, clientError := p.clientAPI.NewClientFromJWT(config, jwtToken, jwtAuth.ServiceID)
|
||||
client, clientError := p.clientAPI.NewClientFromJWT(config, jwtToken, jwtAuth.ServiceID, jwtAuth.HostID)
|
||||
if clientError != nil {
|
||||
return nil, clientError
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ package conjur
|
|||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -33,7 +34,7 @@ type SecretsClient interface {
|
|||
// 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)
|
||||
NewClientFromJWT(config conjurapi.Config, jwtToken string, jwtServiceID, jwtHostID string) (SecretsClient, error)
|
||||
}
|
||||
|
||||
// ClientAPIImpl is an implementation of the ClientAPI interface.
|
||||
|
@ -46,7 +47,7 @@ func (c *ClientAPIImpl) NewClientFromKey(config conjurapi.Config, loginPair auth
|
|||
// 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) {
|
||||
func (c *ClientAPIImpl) NewClientFromJWT(config conjurapi.Config, jwtToken, jwtServiceID, jwtHostID string) (SecretsClient, error) {
|
||||
jwtTokenString := fmt.Sprintf("jwt=%s", jwtToken)
|
||||
|
||||
var httpClient *http.Client
|
||||
|
@ -63,7 +64,13 @@ func (c *ClientAPIImpl) NewClientFromJWT(config conjurapi.Config, jwtToken, jwtS
|
|||
httpClient = &http.Client{Timeout: time.Second * 10}
|
||||
}
|
||||
|
||||
authnJwtURL := strings.Join([]string{config.ApplianceURL, "authn-jwt", jwtServiceID, config.Account, "authenticate"}, "/")
|
||||
var authnJwtURL string
|
||||
// If a hostID is provided, it must be included in the URL
|
||||
if jwtHostID != "" {
|
||||
authnJwtURL = strings.Join([]string{config.ApplianceURL, "authn-jwt", jwtServiceID, config.Account, url.PathEscape(jwtHostID), "authenticate"}, "/")
|
||||
} else {
|
||||
authnJwtURL = strings.Join([]string{config.ApplianceURL, "authn-jwt", jwtServiceID, config.Account, "authenticate"}, "/")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", authnJwtURL, strings.NewReader(jwtTokenString))
|
||||
if err != nil {
|
||||
|
|
|
@ -39,10 +39,13 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
svcURL = "https://example.com"
|
||||
svcUser = "user"
|
||||
svcApikey = "apikey"
|
||||
svcAccount = "account1"
|
||||
svcURL = "https://example.com"
|
||||
svcUser = "user"
|
||||
svcApikey = "apikey"
|
||||
svcAccount = "account1"
|
||||
jwtAuthenticator = "jwt-authenticator"
|
||||
jwtAuthnService = "jwt-auth-service"
|
||||
jwtSecretName = "jwt-secret"
|
||||
)
|
||||
|
||||
func makeValidRef(k string) *esv1beta1.ExternalSecretDataRemoteRef {
|
||||
|
@ -81,27 +84,27 @@ func TestValidateStore(t *testing.T) {
|
|||
},
|
||||
|
||||
{
|
||||
store: makeJWTSecretStore(svcURL, "conjur", "", "jwt-auth-service", "myconjuraccount"),
|
||||
store: makeJWTSecretStore(svcURL, "conjur", "", jwtAuthnService, "", "myconjuraccount"),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
store: makeJWTSecretStore(svcURL, "", "jwt-secret", "jwt-auth-service", "myconjuraccount"),
|
||||
store: makeJWTSecretStore(svcURL, "", jwtSecretName, jwtAuthnService, "", "myconjuraccount"),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
store: makeJWTSecretStore(svcURL, "conjur", "", "jwt-auth-service", ""),
|
||||
store: makeJWTSecretStore(svcURL, "conjur", "", jwtAuthnService, "", ""),
|
||||
err: fmt.Errorf("missing Auth.Jwt.Account"),
|
||||
},
|
||||
{
|
||||
store: makeJWTSecretStore(svcURL, "conjur", "", "", "myconjuraccount"),
|
||||
store: makeJWTSecretStore(svcURL, "conjur", "", "", "", "myconjuraccount"),
|
||||
err: fmt.Errorf("missing Auth.Jwt.ServiceID"),
|
||||
},
|
||||
{
|
||||
store: makeJWTSecretStore("", "conjur", "", "jwt-auth-service", "myconjuraccount"),
|
||||
store: makeJWTSecretStore("", "conjur", "", jwtAuthnService, "", "myconjuraccount"),
|
||||
err: fmt.Errorf("conjur URL cannot be empty"),
|
||||
},
|
||||
{
|
||||
store: makeJWTSecretStore(svcURL, "", "", "jwt-auth-service", "myconjuraccount"),
|
||||
store: makeJWTSecretStore(svcURL, "", "", jwtAuthnService, "", "myconjuraccount"),
|
||||
err: fmt.Errorf("must specify Auth.Jwt.SecretRef or Auth.Jwt.ServiceAccountRef"),
|
||||
},
|
||||
|
||||
|
@ -175,7 +178,22 @@ func TestGetSecret(t *testing.T) {
|
|||
"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"),
|
||||
store: makeJWTSecretStore(svcURL, svcAccount, "", jwtAuthenticator, "", "myconjuraccount"),
|
||||
kube: clientfake.NewClientBuilder().
|
||||
WithObjects().Build(),
|
||||
namespace: "default",
|
||||
secretPath: "path/to/secret",
|
||||
corev1: utilfake.NewCreateTokenMock().WithToken(createFakeJwtToken(true)),
|
||||
},
|
||||
want: want{
|
||||
err: nil,
|
||||
value: "secret",
|
||||
},
|
||||
},
|
||||
"JwtWithServiceAccountRefWithHostIdReadSecretSuccess": {
|
||||
reason: "Should read a secret successfully using a JWT auth secret store that references a k8s service account and uses a host ID.",
|
||||
args: args{
|
||||
store: makeJWTSecretStore(svcURL, svcAccount, "", jwtAuthenticator, "myhostid", "myconjuraccount"),
|
||||
kube: clientfake.NewClientBuilder().
|
||||
WithObjects().Build(),
|
||||
namespace: "default",
|
||||
|
@ -190,11 +208,11 @@ func TestGetSecret(t *testing.T) {
|
|||
"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"),
|
||||
store: makeJWTSecretStore(svcURL, "", jwtSecretName, jwtAuthenticator, "", "myconjuraccount"),
|
||||
kube: clientfake.NewClientBuilder().
|
||||
WithObjects(&corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "jwt-secret",
|
||||
Name: jwtSecretName,
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
|
@ -212,7 +230,7 @@ func TestGetSecret(t *testing.T) {
|
|||
"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"),
|
||||
store: makeJWTSecretStore(svcURL, svcAccount, "", jwtAuthenticator, "", "myconjuraccount"),
|
||||
kube: clientfake.NewClientBuilder().
|
||||
WithObjects().Build(),
|
||||
namespace: "default",
|
||||
|
@ -364,7 +382,7 @@ func makeAPIKeySecretStore(svcURL, svcUser, svcApikey, svcAccount string) *esv1b
|
|||
return store
|
||||
}
|
||||
|
||||
func makeJWTSecretStore(svcURL, serviceAccountName, secretName, jwtServiceID, conjurAccount string) *esv1beta1.SecretStore {
|
||||
func makeJWTSecretStore(svcURL, serviceAccountName, secretName, jwtServiceID, jwtHostID, conjurAccount string) *esv1beta1.SecretStore {
|
||||
serviceAccountRef := &esmeta.ServiceAccountSelector{
|
||||
Name: serviceAccountName,
|
||||
Audiences: []string{"conjur"},
|
||||
|
@ -392,6 +410,7 @@ func makeJWTSecretStore(svcURL, serviceAccountName, secretName, jwtServiceID, co
|
|||
ServiceID: jwtServiceID,
|
||||
ServiceAccountRef: serviceAccountRef,
|
||||
SecretRef: secretRef,
|
||||
HostID: jwtHostID,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -402,7 +421,7 @@ func makeJWTSecretStore(svcURL, serviceAccountName, secretName, jwtServiceID, co
|
|||
}
|
||||
|
||||
func makeStoreWithCA(caSource, caData string) *esv1beta1.SecretStore {
|
||||
store := makeJWTSecretStore(svcURL, "conjur", "", "jwt-auth-service", "myconjuraccount")
|
||||
store := makeJWTSecretStore(svcURL, "conjur", "", jwtAuthnService, "", "myconjuraccount")
|
||||
if caSource == "secret" {
|
||||
store.Spec.Provider.Conjur.CAProvider = &esv1beta1.CAProvider{
|
||||
Type: esv1beta1.CAProviderTypeSecret,
|
||||
|
@ -502,7 +521,7 @@ func (c *ConjurMockAPIClient) NewClientFromKey(_ conjurapi.Config, _ authn.Login
|
|||
return &fake.ConjurMockClient{}, nil
|
||||
}
|
||||
|
||||
func (c *ConjurMockAPIClient) NewClientFromJWT(_ conjurapi.Config, _, _ string) (SecretsClient, error) {
|
||||
func (c *ConjurMockAPIClient) NewClientFromJWT(_ conjurapi.Config, _, _, _ string) (SecretsClient, error) {
|
||||
return &fake.ConjurMockClient{}, nil
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue